Skip to content

fix(chat): catch ProcessTransport errors on interrupt#416

Open
dimakis wants to merge 1 commit into
mainfrom
fix/interrupt-transport-crash
Open

fix(chat): catch ProcessTransport errors on interrupt#416
dimakis wants to merge 1 commit into
mainfrom
fix/interrupt-transport-crash

Conversation

@dimakis

@dimakis dimakis commented Jun 27, 2026

Copy link
Copy Markdown
Owner

Summary

  • Wraps session.queryInstance.interrupt() in try/catch in chat.ts — when the SDK child process has already exited, the call throws ProcessTransport is not ready for writing as an unhandled promise rejection, crashing the Node server
  • Adds .catch() to the fire-and-forget interruptChat() call in ws-handler-v2.ts to prevent unhandled rejections from that path
  • 4 crashes attributed to this bug across June 22-27 (3 on June 27 alone)

Test plan

  • Verify server stays up when interrupting a session whose agent process has exited
  • Verify normal interrupt flow still works (send message while agent is generating)
  • Check logs for the new warn message under the dead-transport scenario

🤖 Generated with Claude Code

When the SDK child process has already exited, calling interrupt() throws
"ProcessTransport is not ready for writing" as an unhandled rejection,
crashing the server. Wrap the call in try/catch — if the transport is
dead the interrupt is moot anyway.

Also add .catch() to the fire-and-forget interruptChat() call in
ws-handler-v2 to prevent any future unhandled rejections from that path.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@dimakis

dimakis commented Jun 27, 2026

Copy link
Copy Markdown
Owner Author

Centaur Review

Found 3 issue(s) (1 warning).

server/chat.ts

Correct defensive fix for a real crash path. The catch in chat.ts preserves post-interrupt logic (inputQueue push), and the .catch in ws-handler-v2 prevents an unhandled rejection on the fire-and-forget call. Main gap is a missing unit test for the new catch branch.

  • 🟡 missing_tests (L1271): No test covers the new try/catch around session.queryInstance.interrupt(). A test where interrupt() rejects (e.g., mockRejectedValue(new Error('process exited'))) should verify that (a) the error is swallowed, (b) interruptChat still returns true, and (c) the inputQueue.push on line 1283 still executes. Without this, there's no regression guard that the catch clause actually preserves the post-interrupt logic. [fixable]
  • 🔵 style (L1274): The comment is good context but slightly long (3 lines). Consider condensing to a single line: // ProcessTransport may already be dead — interrupt is moot. [fixable]

server/index.ts

Correct defensive fix for a real crash path. The catch in chat.ts preserves post-interrupt logic (inputQueue push), and the .catch in ws-handler-v2 prevents an unhandled rejection on the fire-and-forget call. Main gap is a missing unit test for the new catch branch.

  • 🔵 regressions (L911): The v1 WS handler in index.ts:911 also calls interruptChat (via await inside withSpanAsync). It has its own outer try/catch at line 931, but the error message there is generic ('failed to handle WS message') and sends an error frame to the client — different from the fire-and-forget .catch in the v2 handler. Now that interruptChat internally swallows the interrupt() error, the v1 path is also safe, but the inconsistency in error-reporting semantics (v1: error frame to client, v2: silent log) is worth noting for future maintainers.

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