Conversation
`interrupt_for_message` deliberately keeps background `Task` execs running
when the user sends a message mid-turn: `retain` returns `keep_running` for
them, and only the others are aborted. But every id was pushed into
`interrupted_ids` before that decision, so the spared Tasks were marked
interrupted too.
Nothing ever clears that set for an individual id, and it gates the client
event path:
if pending.is_interrupted(message.id).await {
if message.message.as_ref().is_some_and(is_terminal) {
pending.discard_exec(message.id).await;
}
return Ok(ClientExecEvent::Pending);
}
So the Task keeps running on the client, no abort is emitted for it, and the
server then drops every delta it sends and discards the exec outright when
its terminal result arrives. The result never becomes a `ToolCompletion`,
and the tool round waits on a completion that can no longer arrive. The
`keep_running` branch was dead intent.
Mark an exec interrupted only when it is also aborted.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The bug
interrupt_for_messageruns when the user sends a message mid-turn. It deliberately spares backgroundTaskexecs — they are kept in the map and no abort is emitted for them — while aborting everything else:The push happens before the
keep_runningdecision, so the spared Tasks land ininterrupted_idsalongside the aborted ones and are added toself.interrupted. Nothing ever removes an individual id from that set (interrupted_rounds, its neighbour in the conversation layer, is pruned per round; this one is not — only the wholesaledrain_runningclears it).That set gates the client event path:
Failure mode
The Task keeps running on the client — correctly, no
abortis emitted for it — and the server then:ClientExecEvent::Pending), andSo the result never becomes a
ToolCompletion, and the tool round goes on waiting for a completion that can no longer arrive. Thekeep_runningbranch was dead intent: sparing the Task from the abort only changed how it dies.The fix
Mark an exec interrupted only when it is also aborted — one moved line. Interaction ids are unaffected: they are cleared wholesale a few lines below and are still all marked, which the second test pins.
Verification
cargo +1.95 test --package cursor-server --lib cursor::tools::runtimeBefore (
interrupted_ids.push(*id)moved back above the decision, tests kept):After:
The second test is the guard on the other side: a non-Task exec is still aborted and muted, and a pending interaction is still muted.
Gates (
make check, Rust half):cargo fmt --all -- --check— cleancargo clippy --workspace --all-targets -- -D warnings— clean on CI's stable; see notecargo test --workspace --all-targets— all suites greenToolchain note: this machine's
stableis broken, so the gates ran with+1.95. Clippy 1.95 reports acollapsible_matcherror atserver/src/cursor/compile/model.rs:109on unmodifiedmain; that is a 1.95-only false positive (its own suggestion,"fast" if parse_bool(parameter)? =>, does not compile —?is not allowed in a match guard) and CI's 1.98.1 does not emit it, somainis green. Clippy here therefore ran with-A clippy::collapsible_match; this diff is unaffected either way.Related
This is the shape reported in #340 (Multitask: the subtask stops when it completes and the parent never continues), but I have not reproduced that report end to end, so treat it as a lead rather than a claim.