fix: keep background subagents alive across a follow-up cancel - #977
fix: keep background subagents alive across a follow-up cancel#977Sergio-3 wants to merge 1 commit into
Conversation
A turn held open for its background subagents (Turn.deferredSettle) keeps its session/prompt pending, so a client that cancels before each prompt — Zed's run_turn does so unconditionally — reaches cancel() on every ordinary follow-up message, not just on an explicit abort. The interrupt there tore down the very subagents the hold exists to serve, losing their work and the task notifications that would have reported it. Skip the interrupt when the turn settled just above was only parked and nothing else is in flight: no other turn active or queued, and the session idle. Clearing the cancelled latch is part of the skip — while it is set the consumer drops every incoming message, which would lose the surviving subagents' output just as surely. This matches how background shells already behave: they never defer a turn, so no cancel has ever reached them, and both have the same wake-on- notification contract with the model rather than a turn-scoped one. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
055c5a3 to
e096f46
Compare
|
Another data point in favour of this fix, from a different platform — no action needed beyond a review whenever someone has a moment. Environment: Windows 11, Zed 1.16.1, an orchestrator session with several background subagent lanes running. Symptom: matches #976 exactly. Sending an ordinary follow-up message to the orchestrator tears down every in-flight subagent; they report "stopped by the user". No ESC, no stop button, no explicit cancel — the intent was only to talk to the orchestrator while it kept working. Queueing the message isn't a workaround either: while the turn is held open for the background lanes, a queued message never releases. Happy to test a build of this branch against a real multi-lane run on Windows if that would be useful. Thanks for putting the fix together. |
Implements the minimal fix (a) proposed in #976.
Since #870, a turn whose terminal result arrives while the background subagents it spawned are still live is held open, so its
session/promptstays pending. Clients that cancel before each prompt therefore reachcancel()on every ordinary follow-up message — Zed'srun_turncancels unconditionally whenever a turn appears to be running, and a held turn always does — andquery.interrupt()tears down the very subagents the hold exists to serve. Their work is lost, and thetask_notificationthat would have reported completion never fires.This skips the interrupt in the one state where there is provably nothing to interrupt: the turn settled just above was only held open, no other turn is active, orphaned or queued, and the session's last consumed state is
idle. Every other shape still interrupts exactly as before.Clearing the
cancelledlatch is part of the skip rather than an aside: while it is set the consumer drops every incoming message and onlyactivateTurnclears it, so leaving it set would swallow the surviving subagents' output anyway — and permanently if no further prompt follows.The resulting behaviour matches how background shells already work: they never defer a turn, so no cancel has ever reached them.
Tests:
keeps a hold's subagents alive across a cancel on a quiet sessionstill interrupts a cancel during a hold whose session is runningVerified against Zed across a long orchestration session: background subagents now survive follow-up messages, and their completion notifications arrive. The structural option (b) in #976 — releasing the hold once the session goes idle — is intentionally not part of this PR.