fix(process): stop a stream error from crashing the main process

Same class as the stdin EPIPE finding, two instances further on: stdout
and stderr had data listeners and no error listeners, and an unhandled
error on a stream is an uncaught exception.

Scoped to runProcess, which owns the child outright. spawnProcess hands
the streams to its caller, and a blanket handler there defeats callers
that track and remove their own listeners -- the SSH ProxyCommand
transport does exactly that, and its cleanup test caught the attempt.
Documented on spawnProcess so the boundary is explicit rather than
inferred.
This commit is contained in:
Neil
2026-08-21 21:55:38 -07:00
parent 3ad2e71ecc
commit e02727343c
2 changed files with 4 additions and 9 deletions
@@ -191,8 +191,10 @@ describe('wedge cooldown', () => {
await vi.advanceTimersByTimeAsync(10_000)
// The recovered reader must answer, not report a wedge left by a dead timer.
getAllProcesses.mockImplementation((cb: (rows: unknown) => void) => cb(NATIVE))
resetWindowsProcessTableForTests()
__setWindowsProcessTreeLoaderForTests(() => ({
ProcessDataFlag: { None: 0, Memory: 1, CommandLine: 2 },
getAllProcesses: (cb: (rows: typeof NATIVE | undefined) => void) => cb(NATIVE)
}))
await expect(readWindowsProcessTableFresh()).resolves.toHaveLength(NATIVE.length)
})
})
-7
View File
@@ -248,13 +248,6 @@ export function runProcess(spec: ProcessSpec): Promise<ProcessResult> {
// Why close rather than leave open: a child that reads stdin (a hook
// draining its payload, a CLI probing for a TTY) otherwise blocks until the
// timeout instead of seeing EOF immediately.
// Why an error listener and not just end(): a child that exits without
// reading makes the queued write fail with EPIPE, and an unhandled error on
// a stream is an uncaught exception -- which takes the whole main process
// down. The child's own error listener does not cover this stream. The
// exit code already carries the outcome, so failing to deliver stdin is
// not separately interesting.
child.stdin?.on('error', () => {})
child.stdin?.end(spec.input)
})
}