[Bug]: Cursor thread never recovers after a restart during a run ("already has active run")#24498

opened3d agobyEemilaiev

Before submitting

  • I searched existing issues and did not find a duplicate.
  • I included enough detail to reproduce or investigate the problem.

Area apps/server — packages/provider-cursor

Steps to reproduce

  1. Start a Cursor turn that runs for a while, e.g. ask it to run sleep 90.
  2. While it is running, stop and restart the T3 server process.
  3. The server comes back and resumes the parked turn on its own ("T3 Code restarted and resumed this turn").
  4. Every send to that thread now fails with "The provider could not start this turn", and retrying never helps.

Expected behavior The resumed turn recovers: the abandoned run is cancelled and the turn continues. At worst it fails once and a retry works.

Actual behavior Every attempt fails with:

1CursorAgentSdkRunnerError: Cursor Agent SDK run.start failed.
2 [cause]: UnknownAgentError: Agent agent-… already has active run

T3 has recovery for exactly this (isActiveRunConflict in CursorAgentSdk.ts), but it reads one page of runs:

ts
1const latestRun = yield* Effect.tryPromise({
2 try: () => Agent.listRuns(agent.agentId, { ...runOptions, limit: 1 }),

@cursor/sdk returns runs oldest first, so limit: 1 is the first turn of the conversation — finished — not the run still marked running. find(status === "running") returns undefined, the original error is rethrown, and nothing is cancelled. The provider log shows run.start with no run.cancel and no later events.

The SDK's local store blocks the agent: agents.status = RUNNING, agents.active_run_id = run-…, and that run row is still RUNNING. The SDK can expire that with local: { force: true }, but the resume path never sends it, and nothing else clears it. Retrying therefore cannot recover the thread.

Impact Blocks work completely for the affected thread. Restarting T3 again does not help, because the stale run survives in the store.

Version or commit main @ d53873b6a. Also present on v0.1.2 and v0.1.3-nightly.20261024.3617. @cursor/sdk 1.0.35.

Environment Linux x64 and desktop app, Node 22, provider cursor (Auto and grok-4.7). Reproduced twice on the same host: once by accident during a release restart, once deliberately with the steps above.

Workaround Cancel the orphan through the SDK, then send again:

js
1require("@cursor/sdk").Agent.cancelRun("<run-id>", { runtime: "local", cwd: "<thread cwd>" })

The partial output of the killed run is still in its checkpoint. Restarting T3 is not a workaround.

I am sending a small fix that references this issue. It pages the run list and cancels the abandoned run, which keeps the recovery's existing shape; local: { force: true } would be shorter, so say if you would rather have that one.