fix: handle stdin EPIPE when agent exits before reading prompt#50
fix: handle stdin EPIPE when agent exits before reading prompt#50Sayt-0 wants to merge 1 commit into
Conversation
An unhandled 'error' event on child.stdin crashed the whole process when docker-agent died before draining the stdin pipe (prompt larger than the pipe buffer), skipping the retry loop, secret scan, artifact upload and job summary. Attach an 'error' listener before writing so the failure is reported through the normal 'close' exit code path.
derekmisler
left a comment
There was a problem hiding this comment.
🤖 Automated PR Review — this comment was posted by the pr-reviewer bot, not by a human or the implementer agent
Assessment: 🟢 APPROVE
The fix is correct and well-motivated. Attaching an 'error' listener on child.stdin before the write()/end() calls is the right approach — it handles the asynchronous nature of EPIPE on writable streams and ensures the existing retry loop and finally block run normally when the agent exits early. The mock upgrades in both test files are necessary and correct, and the two new regression tests exercise both the survive-EPIPE and retry-after-EPIPE paths.
One minor note (no action required): the listener catches all stdin error codes, not just EPIPE. Any hypothetical unexpected error (e.g. EBADF, EIO) would be swallowed at debug level. Since the child process's close event still surfaces the real exit code, this is unlikely to cause any operational problem — it just slightly reduces observability for novel OS-level conditions. A if (err.code !== 'EPIPE') core.warning(...) guard would preserve that signal if desired, but it's not blocking.
CI: All 8 checks passed. ✅
| // before draining the pipe would crash the whole process; the 'close' | ||
| // event still reports the real exit code. | ||
| if (child.stdin) { | ||
| child.stdin.on('error', (err: Error) => { |
There was a problem hiding this comment.
🤖 Automated PR Review — this comment was posted by the pr-reviewer bot, not by a human or the implementer agent
[LOW] Catch-all stdin error listener silences non-EPIPE errors at debug level
The listener swallows every error emitted by child.stdin, not only EPIPE. An unexpected error code such as EBADF or EIO would be logged at core.debug and then silently discarded. The child close event still delivers the exit code, so the retry loop and caller behavior remain correct — but diagnosing a novel OS-level stdin failure in a future incident would be harder than necessary.
If you'd like to preserve observability without breaking the fix's intent, consider:
child.stdin.on('error', (err: Error) => {
if ((err as NodeJS.ErrnoException).code === 'EPIPE') {
core.debug(`docker-agent stdin write failed: ${err.message}`);
} else {
core.warning(`docker-agent stdin unexpected error: ${err.message}`);
}
});Not blocking — the existing close-based exit-code path is the authoritative failure signal.
Summary
Fixes B5: unhandled EPIPE crash in
src/main/exec.tswhen docker-agent dies before draining stdin (invalid yaml, API key rejected at boot) while the prompt exceeds the pipe buffer (about 64 KB, which review prompts easily exceed).Cause
child.stdinis aWritablestream: an EPIPE fromwrite()is emitted asynchronously as an'error'event on that stream. The existingchild.on('error')handler only covers spawn failures of theChildProcessitself. With no stream listener, Node raisesUnhandled 'error' event: write EPIPEand the whole process crashes, skipping the retry loop, secret scan, artifact upload and job summary (the entirefinallyblock inindex.ts).Changes
src/main/exec.ts'error'listener onchild.stdinbeforewrite()src/main/__tests__/exec.test.tsEventEmitters (the fix calls.on), newmakeEpipeChildhelper, 2 regression testssrc/main/__tests__/main.integration.test.tsThe listener logs at
core.debuglevel rather thancore.warning: EPIPE can also occur when the agent exits successfully without draining stdin, and a warning annotation on a green run would be misleading. The real failure path is already surfaced by the retry loop warnings and the exit code flowing through'close'.Validation
exit 1+ 300 KB prompt) on unfixed codepnpm test(unit)pnpm test:integrationtsc --noEmit,biome ci .,pnpm build