fix(cursor): a closed-stream write in the Cursor SDK no longer stops the server#22897
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
Added 2 commits
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.
Thread transfer impact
✅ Thread transfer remains within every enforced ceiling.
| Provider | Metric | Main baseline | This PR | Impact | PR ceiling | |
|---|---|---|---|---|---|---|
| Codex | Total thread wire | 4.9 KiB | 5.0 KiB | +23 B (+0.5%) | 6.8 KiB | ✅ |
| Codex | Thread snapshot wire | 3.8 KiB | 3.8 KiB | 0 B (0.0%) | 4.9 KiB | ✅ |
| Codex | Live turn WebSocket wire | 1.2 KiB | 1.2 KiB | +23 B (+1.9%) | 2.0 KiB | ✅ |
| Codex | Live turn WebSocket decoded | 20.8 KiB | 20.9 KiB | +41 B (+0.2%) | 29.3 KiB | ✅ |
| Codex | Live turn messages | 1 | 2 | +1 (+100.0%) | 8 | ✅ |
| Claude | Total thread wire | 5.0 KiB | 5.0 KiB | +7 B (+0.1%) | 6.8 KiB | ✅ |
| Claude | Thread snapshot wire | 3.8 KiB | 3.8 KiB | 0 B (0.0%) | 4.9 KiB | ✅ |
| Claude | Live turn WebSocket wire | 1.2 KiB | 1.2 KiB | +7 B (+0.6%) | 2.0 KiB | ✅ |
| Claude | Live turn WebSocket decoded | 21.2 KiB | 21.2 KiB | 0 B (0.0%) | 29.3 KiB | ✅ |
| Claude | Live turn messages | 2 | 2 | 0 (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.
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.tspackages/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
WriteIterableClosedErrorunhandled 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.tsThe 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 name Status Explanation Title check Passed The title clearly and concisely describes the primary fix: preventing a closed-stream Cursor SDK error from stopping the server. Description check Passed 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 explici… Linked Issues check Passed Issue #21418requires containment of the Cursor SDK's asynchronousWriteIterableClosedErrorso it does not terminate the server with exit code 1.CursorSdk.tsadds a process-level rejection guard …Out of Scope Changes check Passed 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, 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
#21418requires containment of the Cursor SDK's asynchronousWriteIterableClosedErrorso it does not terminate the server with exit code 1.CursorSdk.tsadds a process-level rejection guard that accepts only anErrorwith the exact nameWriteIterableClosedError, logs a fixed message, and returns without callingprocess.exit. Other rejections retain the existing exit behavior.CursorSdk.test.tsverifies 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.