Repository navigation
fix(server): a cancelled queued Claude continuation no longer drops later wakes - #30114
juliusmarminge wants to merge 5 commits into
Conversation
…drops later wakes A wake offered while another run is active becomes a queued continuation run. Cancelling it with queued-run.cancel never reaches the Claude adapter, so its requestedContinuations latch stays set and the next wake on that native thread offers no continuation. These tests reproduce #30052. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ater wakes The Claude adapter latches each native thread after offering a wake continuation and released it only when that continuation attached. A queued continuation that ended without starting (cancelled, stopped, settled) never attached, so every later wake on the thread was dropped. ProviderContinuationService now follows the run it dispatched and calls the request's clearIfCurrent when that run ends without starting. The Claude offer supplies a per-offer token so a stale request cannot release a newer wake's latch, and it defers the release to the cancelled wake turn's end so that turn's remaining frames cannot offer it again. Fixes #30052 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Releasing the wake latch judged the wake from whatever was buffered, so a late release could swallow the next wake, an early one re-offered the cancelled wake, and the cancelled wake's frames replayed into the next continuation. The latch now records where its wake ends; release drops that wake's frames and reports and re-offers a later wake, or drops the wake's root frames until its result. The continuation service calls a new opt-in onRunEndedWithoutStarting callback, so only Claude changes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…bagents Retiring a cancelled wake kept every subagent start from the discarded frames, so the next continuation replayed a subagent that had already finished as running forever. Retirement now keeps only starts with no terminal notification or settling root tool result in those frames. A wake cancelled before Claude opened it held its latch until the next root result, which dropped a later task's wake entirely. While the retired wake is unopened, a new task notification now releases the latch so that wake gets its own continuation. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ApprovabilityVerdict: Approved at Macroscope's review found this PR approvable — This restores later Claude background wakes after a queued continuation is cancelled, with per-offer guards preventing stale cleanup from affecting newer wakes. The unresolved Medium subagent-state finding remains subject to the separate blocking gate. Notes:
You can add or adjust custom eligibility rules. Learn more. |
Thread transfer impact✅ Thread transfer remains within every enforced ceiling.
Baseline: Scenario and decoded snapshot size10 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.
Updated in place by a trusted workflow. PR artifacts are strictly validated and never executed. |
|
No actionable comments were generated in the recent review. Walkthrough
Merge Risk:
|
…re-offers itself A cancelled wake's continuation could still go wrong in three ways. A root tool result dropped while the wake was retired left the subagent it settled kept as running. An empty lifecycle result before the wake's real turn was taken as the wake's end, so the real turn got a second continuation. And a subagent notification kept behind the released wake never got its own offer. The retired branch now drops the settled subagent's start, child frames and report; empty task-notification results neither end the latch nor release a retired one; and both release paths offer a buffered subagent notification. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Thanks for the handoff and the real-Claude reproduction on cefb544. The two patches differ only in what happens to the cancelled wake's output:
Whether a cancelled wake's output should be shown with the next wake or dropped is still open. This PR has only been verified with replayed frames; no live Claude session was available here. |
Problem
When a Claude background task wakes the agent while another run is active, T3 queues a continuation run for that wake. If that queued run is cancelled before it starts (the queue's cancel,
t3_queuewithaction: "cancel", or Stop cancelling queued wakes), the Claude adapter'srequestedContinuationslatch for that native thread is never released. Every later wake on that thread is then dropped: Claude keeps working, but no T3 run owns the turn, its messages never reach the thread, and T3 MCP tools in that turn fail withparent_not_active. It lasts until the provider query is reopened or the runtime restarts.Reproduced on main (cefb544) with an integration test that drives the real Claude adapter, orchestrator, effect worker and
ProviderContinuationServicewith replayed SDK frames:queued-run.cancelcancels the queued run.Change
ProviderContinuationRequestgets an optionalonRunEndedWithoutStartingcallback. When a request supplies it,ProviderContinuationServicewatches the run its dispatch created (stored events from the dispatch's own sequence) and calls the callback if that run reaches a terminal status withstartedAt === null. Only Claude supplies it, so ACP/Grok, Muse, Pi and OpenCode are unchanged.clearIfCurrent(archived thread, disabled instance) with the same retire behaviour.result. A later wake already buffered behind it gets its own continuation. Subagenttask_startedframes are kept only for subagents that did not finish inside the dropped frames, and are removed with their child frames if a dropped frame settles them later. Empty lifecycle results (num_turns: 0) do not count as the wake's end.Scope and approval
Fixes #30052
Verification
Focused tests (server-only change, no render path):
ClaudeAutomaticDelivery.integration.test.tsthe wake after a cancelled queued continuation must offer a new continuation: expected [ { …(5) } ] to have a length of 2 but got 1New tests in
Adapters/ClaudeAdapterV2.test.ts("background wake turns"):Each fails on the commit before it and passes at head.
The integration test also asserts that the next continuation carries only its own wake's output, and that two wakes yield exactly two continuation offers.
ClaudeAdapterV2.test.ts,ClaudeAutomaticDelivery.integration.test.tsandProviderContinuationService.test.tsall pass (322 tests). The Pi, Muse, ACP and OpenCode adapter suites pass unchanged.vp run check:changed: format, lint, knip, typecheck (26 packages) and 4 test files pass.Not checked: a live Claude CLI session. The verification VM has no Claude account, so every Claude ordering here is replayed from synthetic SDK frames.
Model and harness: Claude Opus 5.5 (claude-opus-5-5) in Claude Code via T3 Code, with GPT-6.1-Sol in Codex reviewing.
🤖 Generated with Claude Code