fix(cursor): recover restarted threads with older runs#24540

open4
main
fix/issue-24498
opened3d agobyJJulius Marminge
View on GitHubTwo-way sync enabledPR last imported 3d ago

Problem

After a server restart interrupts a Cursor turn, later sends can fail permanently with already has active run. Reproduced on Linux with Cursor Auto by restarting twice during sleep 90: the first recovery cancels its orphan, but the second reads that older cancelled run and misses the currently running one.

Change

Follow the Cursor SDK run-list cursors and select the newest running run before the existing cancel-and-retry recovery. Keep the same resumed-session guard and single recovery attempt. The regression mock now models oldest-first pagination, with the abandoned run after 50 terminal runs.

Scope and approval

Fixes https://github.com/pingdotgg/t3code/issues/24498

GitCafe: https://git.cafe/pingdotgg/t3code/issues/24498

Triaged bug via the automated issue flow. This changes only Cursor server recovery and its focused test; clients and wire contracts are unchanged. The reporter’s existing #24499 is left untouched.

Verification

  • Red regression on main: CursorAgentSdkRunnerError: Cursor Agent SDK run.start failed, caused by already has active run (one failed, one passed).
  • Final focused run after merging main: vp test run src/server/CursorAgentSdk.test.ts in packages/provider-cursor: 2 passed. The regression requires the active run on page two; a mock with no next cursor fails.
  • Scoped lint, format, and provider tsc --noEmit passed.
  • Two independent review rounds (GPT-6.1-Sol and Claude Opus 5.5) approved the final diff; proof audit passed.
EvidenceBeforeAfter
Real web app, Linux x64, Cursor Auto, SDK 1.0.35Runtime transcript: second restart and subsequent send failRuntime transcript: both orphans cancelled, second recovery completes, subsequent send completes

Runtime proof used main 8176b58931 before and 1e2fe076db after. Final head merges current main; the two changed Cursor files are byte-identical to that tested after revision. The clean after run uses its own native agent, consistently across both recoveries and the follow-up. No render paths changed, so the visual matrix is empty. No platform-specific code changed; desktop/mobile shells were not separately exercised.

Models and harnesses: Claude Opus 5.5 through the Claude harness (tests and implementation); GPT-6.1-Sol through Codex and Claude Opus 5.5 through Claude (reviews); GPT-6.1-Sol through Codex (issue lead and proof).

Checks

22 successful · 6 skipped
All checks passed
340cab4

Added 4 commits

  • test(cursor): cover abandoned run behind an older cancelled run04d7662
  • test(cursor): cover abandoned run on a later listRuns page1e2fe07
  • Merge main into fix/issue-24498340cab4
  • fix(cursor): restart recovery finds the abandoned run behind older runsdc89d0c
Synced from GitHub

Approvability

Verdict: Approved at 340cab4

Macroscope's review found this PR approvable — This is a narrowly scoped Cursor recovery bug fix that paginates older local runs only when a resumed send encounters an active-run conflict, then preserves the existing cancel-and-retry flow. Normal execution paths and contracts remain unchanged, and the page-two case is covered by a focused regression test.

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 wire5.0 KiB5.0 KiB0 B (0.0%)6.8 KiB✅
CodexThread snapshot wire3.8 KiB3.8 KiB0 B (0.0%)4.9 KiB✅
CodexLive turn WebSocket wire1.2 KiB1.2 KiB0 B (0.0%)2.0 KiB✅
CodexLive turn WebSocket decoded20.9 KiB20.9 KiB0 B (0.0%)29.3 KiB✅
CodexLive turn messages220 (0.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: bf5c483 · PR result: 340cab4 · 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.

External image Recent review info
External image Run configuration
External image Commits

Reviewing files that changed from the base of the PR and between bf5c483cbe298032fe5f97b05f31f8458aab0031 and 340cab4575f5458e9c57f7af6d4cdbcb09045af3.

External image Files selected for processing: 2
  • packages/provider-cursor/src/server/CursorAgentSdk.test.ts
  • packages/provider-cursor/src/server/CursorAgentSdk.ts

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

Walkthrough

The Cursor runner now paginates through run results during active-run recovery, finds a running run beyond the first page, and cancels it before retrying the send.

Changes: Cursor run recovery

Layer / File(s)Summary
Paginated abandoned-run recovery
packages/provider-cursor/src/server/CursorAgentSdk.ts, packages/provider-cursor/src/server/CursorAgentSdk.test.ts
The runner checks all run-list pages and retains the last running run found. The test mock models paginated results and cancellation. A new test verifies cancellation of a running run on the second page and a retry of the send.

Priority: External image Normal

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

Change: Bug fix · Severity of issue fixed: Medium

Suggested labels: bugfix

Suggested reviewers: nkoynov, macroscopeapp

Merge Risk: External image Minimal · up to 340ca

The change is ready to merge after normal checks; no actionable recovery issue remains established.

Pre-merge checks: Passed 4
Check nameStatusExplanation
Title checkPassedThe title is concise, specific, and accurately describes the Cursor recovery fix for restarted threads with older runs.
Description checkPassedThe description covers the problem, change, scope and approval, and verification. It includes reproduction details, test results, runtime evidence, and known verification limits.
Linked Issues checkPassed[#24498] Requirement: when a restart leaves a running run, the recovery must find and cancel that run, then let the turn continue. The reviewed head changes the recovery in packages/provider-cursor/sr…
Out of Scope Changes checkPassedAll changed code lies in packages/provider-cursor: CursorAgentSdk.ts (abandoned-run recovery pagination and a Run type import) and CursorAgentSdk.test.ts (mock pagination, the updated listRuns exp…
Full details: Linked Issues check

Explanation

[#24498] Requirement: when a restart leaves a running run, the recovery must find and cancel that run, then let the turn continue. The reviewed head changes the recovery in packages/provider-cursor/src/server/CursorAgentSdk.ts. The Agent.listRuns call no longer uses limit: 1. The code now follows nextCursor until no cursor remains. For each page it keeps page.items.findLast(status === "running"), and a later page's match replaces an earlier one. The newest running run is therefore selected, which covers the case where an older cancelled run sits on the first page. The existing cancel-and-retry path and the resumed-session guard are unchanged in the visible hunk. The test file adds a mock that models oldest-first cursor pagination with a page size of 50. A new test places the running run after 50 terminal runs and asserts that cancelRun is called with that run id and that the last operation is run.start. Limits: I did not execute the test suite. The PASS rests on the diff and the test code. The unchanged recovery path after activeRun is selected was not read in full, but the test asserts the cancel call with the selected run id.

Full details: Out of Scope Changes check

Explanation

All changed code lies in packages/provider-cursor: CursorAgentSdk.ts (abandoned-run recovery pagination and a Run type import) and CursorAgentSdk.test.ts (mock pagination, the updated listRuns expectation without limit: 1, and one new regression test). Each hunk supports the issue objective of finding the active run behind older runs. No client, contract, or unrelated server change appears in the reviewed diff. The PR description says the two changed Cursor files match the tested revision, and no ignored files apply.

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.