Skip to content

fix(core): ignore late EPIPE on stdin in the cross-spawn spawner - #393

Merged
filipeforattini merged 2 commits into
mainfrom
hook-epipe-recur
Sep 23, 2026
Merged

filipeforattini merged 2 commits into
mainfrom
hook-epipe-recur

Conversation

@filipeforattini

@filipeforattini filipeforattini commented Sep 23, 2026 •

Copy link
Copy Markdown

Root cause

The HookV2 tests in packages/core/test/hook.test.ts failed again with EPIPE: broken pipe, write after #360. This was the first CI attempt of PR #378: run 35824326717, attempt 1, job 107062725644. The stack shows where it comes from:

EPIPE: broken pipe, write
  at end (unknown)
  at internal:fs/streams:402:23      <- WriteStream._destroy -> fileSink.end()
  at _destroy (internal:streams/destroy:63:18)
  at finish (internal:streams/writable:482:21)
  at processTicksAndRejections

Bun keeps a small stdin write buffered in its FileSink and flushes it only when the stream is destroyed after end() (autoDestroy on finish). If the hook exits without reading its input, that flush fails with EPIPE. By then the stdin sink has completed and removed its error listener, so the stream's error event has no listener and becomes an uncaught exception.

#360 fixed this only in @effect/platform-node-shared's NodeChildProcessSpawner. Core does not use that spawner. AppProcess (and so HookV2.executeCommand) runs on CrossSpawnSpawner in packages/core/src/cross-spawn-spawner.ts, which has its own copy of setupStdin without the listener.

I reproduced it locally with a short Bun script: a child that closes fd 0, then stdin.write(small) and end(). It raises the same uncaught EPIPE with the same stack on every run.

Fix

  • CrossSpawnSpawner.setupStdin now attaches a no-op error listener to the child's stdin, the same as the fix(core): ignore late EPIPE from hook stdin #360 patch. The sink still reports errors while it is attached.
  • Deterministic regression test in packages/core/test/effect/cross-spawn-spawner.test.ts. The child closes its stdin and reports it on stdout. Only then does the stdin stream emit its JSON payload. The test fails if any uncaught exception is recorded.

Validation

CI only.


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.

@filipeforattini
filipeforattini enabled auto-merge (squash) September 23, 2026 18:06
@filipeforattini
filipeforattini merged commit 0738e58 into main Sep 23, 2026
22 checks passed
@filipeforattini
filipeforattini deleted the hook-epipe-recur branch September 23, 2026 18:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant