Skip to content

fix(server): child exiting with unread fd input no longer crashes the server - #58

Merged
alexcstark merged 1 commit into
mainfrom
stark/chrome-fd3-crash
Oct 11, 2026
Merged

alexcstark merged 1 commit into
mainfrom
stark/chrome-fd3-crash

Conversation

@alexcstark

Copy link
Copy Markdown
Owner

The lax6e remote server crashed 9 times since Oct 8, dropping every connected client each time. Every crash had the same trace: Error: read ECONNRESET at Pipe.onStreamRead emitted as an unhandled 'error' event on a Socket, then systemd restarted the service.

Cause: headless Chrome (used for HTML previews) talks CDP over --remote-debugging-pipe, which uses extra stdio fds 3 and 4. Node opens extra fds as duplex sockets and reads from them, even ones we only write to. On Linux, when a child exits with unread data in its fd 3 receive buffer, our end of the socket gets reset. @effect/platform-node-shared's spawner only listens for errors on an input fd while a write is in flight, so an idle fd 3 has no listener and the reset kills the process. macOS doesn't reset the socket here, which is why only the Linux remote crashed.

Fix: patch @effect/platform-node-shared@4.0.2 to keep an error listener on input fds for their whole life. Write failures still reach the sink's own listener, and the child's exit is still reported through exitCode. Patching the spawner fixes every caller, including DesktopBackendManager's input fds, not only Chrome.

Verification:

  • 6-line raw Node repro and a bundled Effect repro using headlessChrome's exact spawn config both crash on lax6e (Linux, Node 22) and survive on macOS.
  • The new test's sequence, bundled and run on lax6e: unpatched crashes 3/3 with read ECONNRESET, patched passes 3/3.
  • vp test run src/process/childProcessFds.test.ts passes locally (macOS can't reproduce the crash, so CI on ubuntu is the run that guards it). apps/server typecheck is clean.

Not covered: child stdin has the same "listener only while writing" shape, but Node doesn't read from it, so it can't produce this read reset. I left it alone.

🤖 Generated with Claude Code

… server

On Linux, Node opens extra stdio fds as duplex sockets that it also reads.
Effect's spawner only listens for errors on an input fd while a write is in
flight, so when headless Chrome exited with unread CDP input on fd 3 the
socket reset (read ECONNRESET) was an unhandled 'error' event and killed the
whole server. Patch @effect/platform-node-shared to keep a listener on input
fds for their whole life; write failures still reach the sink.
@github-actions github-actions Bot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:S labels Oct 11, 2026
@alexcstark
alexcstark merged commit 67d59c1 into main Oct 11, 2026
23 of 24 checks passed
@alexcstark
alexcstark deleted the stark/chrome-fd3-crash branch October 11, 2026 15:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:S vouch:trusted PR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant