fix(workstreams): handle EPIPE when gh exits before reading stdin - #7
Merged
Merged
Conversation
main has been red since 2026-09-26 with every test passing. The
`quality_checks (workstreams)` job reports 61 files and 990 tests all
green, then fails anyway:
Vitest caught 3 unhandled errors during the test run.
Error: write EPIPE
❯ host.ts:78:20 child.stdin?.end(stdin ?? "");
ghRunner spawns `gh` and immediately ends its stdin. When `gh` exits
before reading — an unauthenticated or immediately-failing invocation
does exactly that, which is why this reproduces on a runner and not on
a developer machine where `gh` is logged in — the write lands on a
closed pipe. EPIPE is then raised asynchronously on the stream, and
with no "error" listener attached it surfaces as a process-level
uncaught exception instead of an error on this call. Vitest counts
that as an unhandled error and exits non-zero even though no
assertion failed.
Attaching a no-op error listener is the fix. The write error carries
no information worth acting on: the execFile callback above already
resolves {ok:false} with the child's real stderr, so the genuine
failure is still reported.
Verified:
- reproduced deterministically outside vitest: execFile of a binary
that never reads stdin, then end() of a 1MB payload, raises an
uncaught EPIPE and exits non-zero
- with the listener attached the same script exits 0, and the
execFile callback still fires, so ghRunner still resolves
- host.ts:78 is the only stdin write in the repo, and there were no
existing stdin error handlers anywhere
- tsc --noEmit clean; vitest run: 61 files, 990 tests, all passing
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018DDkZGNoSVspVq1Z9jcVtP
mjsz
approved these changes
Sep 28, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
mainhas been red since 2026-09-26 while every test passes. Thequality_checks (workstreams)job reports 61 files and 990 tests green, then fails anyway:Root cause
ghRunnerspawnsghand immediately ends its stdin. Whenghexits before reading it — an unauthenticated or immediately-failing invocation does exactly that, which is why this reproduces on a runner but not on a developer machine whereghis logged in — the write lands on a closed pipe.EPIPE is raised asynchronously on the stream. With no
"error"listener attached it surfaces as a process-level uncaught exception rather than an error on this call, and vitest counts that as an unhandled error and exits non-zero even though no assertion failed.The fix
A no-op error listener on
child.stdin. The write error carries nothing worth acting on — theexecFilecallback above already resolves{ok:false}with the child's real stderr, so genuine failures are still reported unchanged.Verified
execFilea binary that never reads stdin, thenend()a 1MB payload → uncaught EPIPE, non-zero exitexecFilecallback still fires — soghRunnerstill resolves correctlyhost.ts:78is the only stdin write in the repo, and there were no existing stdin error handlers anywheretsc --noEmitclean;vitest run→ 61 files, 990 tests, all passingFound during a maintenance sweep — a persistently red main masks every future real failure in this repo, which is why it was worth chasing despite no test actually failing.
🤖 Generated with Claude Code
https://claude.ai/code/session_018DDkZGNoSVspVq1Z9jcVtP