Skip to content

fix(server): keep daemon alive when JSONL RPC stdin hits EPIPE - #4048

Merged
boudra merged 3 commits into
getpaseo:mainfrom
Elliotwu-7:fix/jsonl-rpc-stdin-epipe
Aug 31, 2026
Merged

boudra merged 3 commits into
getpaseo:mainfrom
Elliotwu-7:fix/jsonl-rpc-stdin-epipe

Conversation

@Elliotwu-7

Copy link
Copy Markdown
Contributor

Linked issue

Closes #4047

Related: #550 (same crash class, WebSocket-only fix)

Type of change

  • Bug fix
  • New feature
  • Enhancement
  • Refactor
  • Docs

Reasoning

A dead Claude/OMP JSONL child currently kills the whole daemon. JsonlRpcProcess.send() writes to child.stdin with no error listener and no try/catch. Node treats that write EPIPE as uncaughtException, 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 fatal write EPIPE frames from JsonlRpcProcess.send, then Claude runtime exited unexpectedly with code 143, then Worker exited (code 1).

This PR keeps the failure on that transport. The agent turn fails; the daemon stays up.

Goals

  • stdin.write() throwing EPIPE does not become process-level uncaughtException
  • a stdin 'error' event does not become process-level uncaughtException
  • a non-writable stdin fails the in-flight request immediately instead of hanging until the RPC timeout
  • later requests on that transport reject with JSONL RPC process is closed

Non-goals

  • Changing provider cancel/SIGTERM behavior
  • Guarding stdout/stderr error events
  • Changing how the agent UI presents a failed turn

QA

Production crash (desktop-managed daemon 0.7.0-beta.2, macOS arm64, Node 24.14.0 inside Electron 41.2.0):

Uncaught exception — daemon crashing
Error: write EPIPE
    at afterWriteDispatched (node:internal/stream_base_commons:159:15)
    at Writable.write (node:internal/streams/writable:508:10)
    at JsonlRpcProcess.send (.../jsonl-rpc-process.js:105:26)
    at new Promise (<anonymous>)
Claude runtime exited unexpectedly  code=143
Worker exited (code 1). Supervisor shutting down.

Regression tests added in jsonl-rpc-process.test.ts for the three Node paths: sync write() throw, stdin 'error' event, and writable === false.

$ npx vitest run packages/server/src/server/agent/providers/jsonl-rpc-process.test.ts --bail=1 --reporter=verbose
 Test Files  1 passed (1)
      Tests  13 passed (13)
   Duration  774ms

Hook on commit: lint, format, and workspace typecheck passed.

Platform Tested Notes
iOS no daemon-only change
Android no daemon-only change
Web no daemon-only change
Desktop macOS yes unit tests + production stack from local daemon.log
Desktop Windows no same Node stdin APIs; not run here
Desktop Linux no same Node stdin APIs; not run here

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 the send() call site, and an 'error' event with no listener).

Checklist

  • One focused change
  • npm run typecheck passes
  • npm run lint passes
  • npm run format passes
  • QA evidence
  • Tests added or updated where it made sense

Elliotwu-7 and others added 3 commits August 30, 2026 03:19
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>
@boudra
boudra merged commit 74a377f into getpaseo:main Aug 31, 2026
23 checks passed
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>
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.

Daemon crashes on uncaught write EPIPE from JsonlRpcProcess.send

2 participants