Where does the bug appear (feature/product)?
Something else… — this is not the Cursor IDE/CLI itself, but the @cursor/sdk TypeScript SDK’s local agent runtime (the programmatic Agent/run.stream() API used to build a custom self-hosted chat frontend on top of Cursor, running inline in a Node process against SqliteLocalAgentStore, no cloud runtime involved).
Describe the Bug
When the event stream between the local SDK and the backend stalls and the SDK internally retries (bumping an internal “attempt generation” counter), any toolCallCompleted message that arrives tagged with the stale (pre-retry) generation is silently dropped instead of being reconciled with the new attempt — only a debug-level warning (nal.await_stall.stale_completion_dropped) is logged internally, nothing is surfaced to the SDK consumer. Because toolCallStarted (tool status running) was already emitted and persisted before the stall, the affected tool call is left permanently stuck at running, with no completed event ever following — even for trivial, fast operations (reproduced with a single edit call on a small file). The parent run never transitions out of RUNNING; there is no error, timeout, or any signal to the caller that something went wrong. The only recovery is for the caller to notice the run has gone silent (via wall-clock heuristics) and manually call run.cancel().
Relevant code path (@cursor/[email protected], dist/esm/357.js, minified but consistent):
function ce(e, t, r) {
return {
sendUpdate: async (n, o) => {
if (t === r()) return e.sendUpdate(n, o);
"toolCallCompleted" === o.message?.case &&
se.warn(n, "nal.await_stall.stale_completion_dropped", { attemptGen: t, currentGen: r() })
},
...
}
}
t is the generation the completion belongs to, r() is the current generation; on mismatch the completion is dropped, never re-requested or converted into a synthetic failure for the new generation. Separately, the stall/retry policy (fe(...)) does eventually throw a "Connection stalled repeatedly" ConnectError after a retry cap — but that error does not appear to propagate into the run’s terminal status in either reproduction below; the run simply stays RUNNING forever.
Steps to Reproduce
We don’t have a deterministic repro (it depends on a transient stream stall), but the pattern is consistent across two independent occurrences:
- Start a local-runtime
Agent, send a message that triggers a normal multi-step tool-use turn (any mix ofread/grep/edit/shell). - If the underlying stream stalls at any point while a tool call is in flight, the SDK retries the turn internally (visible in the model’s own
thinkingstream as it notices it’s not getting a result and retries the same logical tool call under a newcall_id). - Inspect the persisted event log for that
run(SQLiterun_eventstable under~/.cursor/projects/<workspace>/sdk-agent-store/<hash>/index.db): the original tool call’stoolCallStarted/runningevent is present, but its matchingtoolCallCompletednever arrives — and this is silent, not surfaced anywhere in the public stream/API. - The
runstays inRUNNINGindefinitely; no application-visible error occurs.
Observed twice:
- Run A: 3 tool calls (
grep×2,read×1) got stuck asrunningat the very start of a turn; the run ran 38.6 minutes total before eventually completing on newcall_ids for those same logical calls. - Run B: a single
editcall’scall_idappears twice withstatus: "running"in the event log (the SDK’s own retry), and never gets acompletedevent for either attempt. Confirmed on disk that the target file was never written. The run has been stuck inRUNNINGfor 10+ minutes with zero further stream activity at time of writing.
Expected Behavior
Either:
- the stale
toolCallCompletedshould be reconciled against the current generation (re-emitted / re-associated) instead of being silently dropped, or - if reconciliation isn’t possible, the tool call should be marked as failed/unknown for the current generation so the turn can proceed or terminate cleanly, or
- at minimum, the stall+retry+drop event should be surfaced through the public event stream (not just an internal log), and the eventual
"Connection stalled repeatedly"error (which the retry policy already throws internally) should propagate to therun’s terminal status instead of leaving it inRUNNINGforever.
Screenshots / Screen Recordings
N/A — this manifests only in the underlying local SQLite event log (run_events table), not in any UI. Happy to attach raw JSON excerpts of the toolCallStarted/toolCallCompleted sequences for both incidents on request.
Operating System
Linux (Ubuntu, kernel 5.15.0-179-generic, x86_64)
Version Information
Not applicable in the IDE/CLI sense — this is the @cursor/sdk npm package used programmatically:
Package: @cursor/sdk
Version: 1.0.23 (latest on npm as of 2026-07-03; no newer release available)
Node.js: v22.22.0
Runtime: local (Agent inline in Node process, SqliteLocalAgentStore), not cloud/Background Agent
For AI issues: which model did you use?
Reproduced with grok-4.5; also reproduced with composer-2.5 in the same workspace on other runs.
For AI issues: add Request ID with privacy disabled
Not available — this isn’t driven through the Cursor IDE UI, so there’s no IDE Request ID. Closest equivalents from our own logs:
Agent ID (Run A / claude-code-web-chat workspace): agent-... (Turn 11, "会话管理功能询问")
Agent ID (Run B / cursor-web-chat workspace): agent-f0f50773-c5ec-46b8-a4f3-73776c70e098
Run ID (Run B): run-516c8648-e2a2-470b-a3ef-e0e28026ada0
Call ID (stuck edit, Run B): call-2afbf73f-2c8e-46e4-b286-36c84137878f-35
Additional Information
This is easy to conflate with a separate, unrelated-but-similarly-confusing behavior we also found in the same SDK: a shell timeout in TIMEOUT_BEHAVIOR_BACKGROUND mode does not kill the process — it gets backgrounded and keeps running, later reporting status: "success" with the full executionTime, even if that’s 15+ minutes past the declared timeout. That one is at least “working as designed” if undocumented; the stale-completion-drop issue described here is a real bug with no recovery path other than an external watchdog manually cancelling the run.
Does this stop you from using Cursor?
Sometimes - I can sometimes use Cursor (workaround: an external script polls the local SQLite store for runs stuck in RUNNING with no recent event activity and force-cancels them; without that, affected runs hang forever and block the session)