From ba011a30461eb460c67db95d8f81dd74e636c452 Mon Sep 17 00:00:00 2001 From: terra tauri Date: Sun, 27 Sep 2026 01:35:06 -0700 Subject: [PATCH] fix(workstreams): handle EPIPE when gh exits before reading stdin MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) Claude-Session: https://claude.ai/code/session_018DDkZGNoSVspVq1Z9jcVtP --- plugins/workstreams/host.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/plugins/workstreams/host.ts b/plugins/workstreams/host.ts index b141653..2aebf07 100644 --- a/plugins/workstreams/host.ts +++ b/plugins/workstreams/host.ts @@ -75,6 +75,13 @@ function ghRunner(signal: AbortSignal): GhRunner { resolve({ ok: true, stdout: stdout.toString() }); }, ); + // `gh` can exit before it ever reads stdin — an unauthenticated or + // immediately-failing invocation does exactly that. Writing to the + // closed pipe then raises EPIPE asynchronously, and with no listener on + // the stream it surfaces as a process-level uncaught exception rather + // than an error on this call. The execFile callback above still reports + // the real failure, so the write error is genuinely nothing to act on. + child.stdin?.on("error", () => {}); child.stdin?.end(stdin ?? ""); }); }