fix(server): keep daemon alive when JSONL RPC stdin hits EPIPE - #4048
Merged
Merged
Conversation
A dead Claude/OMP child currently turns stdin.write() EPIPE into uncaughtException and kills the daemon. Catch the write, listen for stdin errors, and close that transport instead. Closes getpaseo#4047 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
mcowger
pushed a commit
to mcowger/paseo
that referenced
this pull request
Aug 31, 2026
…seo#4048) * fix(server): keep daemon alive when JSONL RPC stdin hits EPIPE A dead Claude/OMP child currently turns stdin.write() EPIPE into uncaughtException and kills the daemon. Catch the write, listen for stdin errors, and close that transport instead. Closes getpaseo#4047 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * test(server): reproduce JSONL RPC stdin EPIPE * test(server): gate native EPIPE fixture on POSIX --------- Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com> Co-authored-by: Mohamed Boudra <boudra.moha@gmail.com>
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.
Linked issue
Closes #4047
Related: #550 (same crash class, WebSocket-only fix)
Type of change
Reasoning
A dead Claude/OMP JSONL child currently kills the whole daemon.
JsonlRpcProcess.send()writes tochild.stdinwith no error listener and no try/catch. Node treats thatwrite EPIPEasuncaughtException, the worker exits code 1, and every other agent/session on the host goes with it.#550 already guarded WebSocket send paths. The JSONL RPC stdin pipe used by Claude and OMP was still uncovered. I hit this on desktop-managed
0.7.0-beta.2: two fatalwrite EPIPEframes fromJsonlRpcProcess.send, thenClaude runtime exited unexpectedlywith code 143, thenWorker exited (code 1).This PR keeps the failure on that transport. The agent turn fails; the daemon stays up.
Goals
stdin.write()throwingEPIPEdoes not become process-leveluncaughtException'error'event does not become process-leveluncaughtExceptionJSONL RPC process is closedNon-goals
QA
Production crash (desktop-managed daemon
0.7.0-beta.2, macOS arm64, Node 24.14.0 inside Electron 41.2.0):Regression tests added in
jsonl-rpc-process.test.tsfor the three Node paths: syncwrite()throw, stdin'error'event, andwritable === false.Hook on commit: lint, format, and workspace typecheck passed.
I did not reproduce the crash against a live Claude CLI in this checkout. The two in-memory cases are the Node failure modes from the production stack (
write()throw at thesend()call site, and an'error'event with no listener).Checklist
npm run typecheckpassesnpm run lintpassesnpm run formatpasses