fix(cursor): a closed-stream write in the Cursor SDK no longer stops the server#22897

merged2
main
takeover/21447-cursor-closed-stream
opened25d agobyTtheo
View on GitHubTwo-way sync enabledPR last imported 25d ago

When a Cursor local exec fails after its stream has closed, the Cursor SDK rejects a promise nothing awaits with WriteIterableClosedError. T3 Code's guard treats any unknown rejection as fatal and calls process.exit(1), so the whole server stops and every thread on every provider goes down with it.

Takeover of #21447 by @MarMarLabs. Their commit is kept with its authorship. I replaced the 119-line child-process probe test with a small in-process test of the guard, and made the matcher private since only the guard uses it.

The guard now also lets this one error through: it matches an Error whose name is exactly WriteIterableClosedError (the SDK class is minified, so the name is the only stable mark) and logs a fixed message without the error text. Every other rejection still exits. This is server-only, so web, desktop, and mobile clients need no change. Only the Cursor provider has this guard.

Proof: CursorSdk.test.ts builds the real layer, calls the guard it installs, and checks process.exit. "keeps the server running when the SDK writes to a closed stream" fails on main (expected [ [ 1 ] ] to deeply equal []) and passes here. "still exits on other rejections" passes on both. Ran vp test run on that file and vp run check:changed (format, lint, knip, typecheck, tests all pass). Not checked: a live Cursor exec hitting this race, and Windows.

Closes #21418

Made by Claude Opus 5.5 in Claude Code, running in T3 Code. 🤖 Generated with Claude Code

Checks

24 successful · 9 skipped
All checks passed
dcec962

Added 2 commits

  • fix(cursor): keep the server running when the SDK writes to a closed stream1de4b77
  • test(cursor): check the closed-stream guard in process instead of a child probedcec962
Synced from GitHub

Approvability

Verdict: Approved at dcec962

Macroscope's review found this PR approvable — This is a small, self-contained Cursor server bug fix that prevents one identified closed-stream SDK rejection from terminating the process while preserving existing handling for other rejections. A focused regression test covers both behaviors, with no API, schema, or product-default changes.

You can add or adjust custom eligibility rules. Learn more.

Synced from GitHub

Thread transfer impact

✅ Thread transfer remains within every enforced ceiling.

ProviderMetricMain baselineThis PRImpactPR ceiling
CodexTotal thread wire4.9 KiB5.0 KiB+23 B (+0.5%)6.8 KiB✅
CodexThread snapshot wire3.8 KiB3.8 KiB0 B (0.0%)4.9 KiB✅
CodexLive turn WebSocket wire1.2 KiB1.2 KiB+23 B (+1.9%)2.0 KiB✅
CodexLive turn WebSocket decoded20.8 KiB20.9 KiB+41 B (+0.2%)29.3 KiB✅
CodexLive turn messages12+1 (+100.0%)8✅
ClaudeTotal thread wire5.0 KiB5.0 KiB+7 B (+0.1%)6.8 KiB✅
ClaudeThread snapshot wire3.8 KiB3.8 KiB0 B (0.0%)4.9 KiB✅
ClaudeLive turn WebSocket wire1.2 KiB1.2 KiB+7 B (+0.6%)2.0 KiB✅
ClaudeLive turn WebSocket decoded21.2 KiB21.2 KiB0 B (0.0%)29.3 KiB✅
ClaudeLive turn messages220 (0.0%)8✅

Baseline: e822ba7 · PR result: dcec962 · Source CI: success

Scenario and decoded snapshot size

10 historical turns, 5 command tools per turn, 878.9 KiB retained MCP result per historical turn, and a 1.05 MiB retained result in the measured turn.

  • Codex decoded thread snapshot: 108.5 KiB
  • Claude decoded thread snapshot: 108.8 KiB

Updated in place by a trusted workflow. PR artifacts are strictly validated and never executed.

Synced from GitHub

Review in Change Stack →

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Path: .coderabbit.config.ts
  • Review profile: CHILL
  • Plan: Team
  • Run ID: 877e4c66-04c1-4438-b056-e4721e76232c
📥 Commits

Reviewing files that changed from the base of the PR and between e822ba78ea23612f57093235f84813f385cf6431 and dcec9629a1ff0102971e89b3aeb72de0aa0f2b8d.

📒 Files selected for processing: 2
  • packages/provider-cursor/src/server/CursorSdk.test.ts
  • packages/provider-cursor/src/server/CursorSdk.ts

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 3 remain after this review.

📝 Walkthrough

The Cursor server now handles SDK-shaped WriteIterableClosedError unhandled rejections without exiting. Tests verify that other errors with the same message still cause an exit with status 1.

Changes: Cursor rejection handling

Layer / File(s)Summary
Closed-stream rejection handling
packages/provider-cursor/src/server/CursorSdk.ts, packages/provider-cursor/src/server/CursorSdk.test.ts
The handler detects errors named WriteIterableClosedError, logs the error, and returns instead of terminating the server. Tests check this behavior and confirm that a generic error with the same message still exits with status 1.

Priority: ➖ Normal

Estimated code review effort: 2 (Simple) | ~10 minutes

Change: Bug fix · Severity of issue fixed: Medium

Suggested reviewers: maria-rcks

Merge Risk: ⚪ Minimal · up to dcec9

The Cursor closed-stream guard appears mergeable after normal checks; no actionable merge-blocking risk is established.

Pre-merge checks: Passed 4
Check nameStatusExplanation
Title checkPassedThe title clearly and concisely describes the primary fix: preventing a closed-stream Cursor SDK error from stopping the server.
Description checkPassedThe description covers the problem, implementation, scope, issue reference, verification results, untested conditions, and agent disclosure. It does not use the template headings or include an explici…
Linked Issues checkPassedIssue #21418 requires containment of the Cursor SDK's asynchronous WriteIterableClosedError so it does not terminate the server with exit code 1. CursorSdk.ts adds a process-level rejection guard …
Out of Scope Changes checkPassedThe whole-PR diff contains only the Cursor server guard and its focused automated tests. Both changes support issue #21418. The guard remains limited to the Cursor provider and does not change web, de…
Full details: Description check

Explanation

The description covers the problem, implementation, scope, issue reference, verification results, untested conditions, and agent disclosure. It does not use the template headings or include an explicit maintainer approval comment, but it is otherwise mostly complete and directly related to the change.

Full details: Linked Issues check

Explanation

Issue #21418 requires containment of the Cursor SDK's asynchronous WriteIterableClosedError so it does not terminate the server with exit code 1. CursorSdk.ts adds a process-level rejection guard that accepts only an Error with the exact name WriteIterableClosedError, logs a fixed message, and returns without calling process.exit. Other rejections retain the existing exit behavior. CursorSdk.test.ts verifies both the non-exit case and the exit-on-other-errors case.

Full details: Out of Scope Changes check

Explanation

The whole-PR diff contains only the Cursor server guard and its focused automated tests. Both changes support issue #21418. The guard remains limited to the Cursor provider and does not change web, desktop-client, mobile-client, or unrelated provider behavior.

✨ Finishing Touches
📝 Generate docstrings
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Comment @coderabbitai help to get the list of available commands.