Repository navigation
Description
What happened
A Claude parent thread stayed on "Thinking" for hours after a short, finished turn. All later messages
and scheduled-task wakes queued behind that turn, and none of them started. The parent had sent a
message to an already-finished delegated child with t3_thread_send and then ended its turn. About 15 s
later the child answered. Its completion overwrote the parent run, which had just reached completed,
back to waiting. The completion notice was then queued behind that run, so the two wait for each
other indefinitely.
Diagnosis
Two writers update the same run row without coordination, and one of them writes a stale full-row snapshot.
CheckpointCaptureServiceloads the run while it is stillwaiting(:68). It takescapturedAt
(:106), captures the checkpoint, and then commits{...run, status: "completed", completedAt, checkpointId}(:203). It does not take the thread-dispatch lock.finalizeAppOwnedSubagentTask(Orchestrator.ts:10131) runs under the parent lock (:11294). It
reads the parent run, andplanDelegatedCompletionDelivery(:9925) returns
{...input.parentRun, delegatedCompletion}. That object is written as a full-rowrun.updated(:10338).preserveRunRecordedFields(ProjectionStore.ts:646) protects onlydelegatedCompletionand
restartCancelledBackgroundWorkfrom stale snapshots.status,completedAtandcheckpointIdcome
from the payload unchanged. Checkpoint capture explicitly omitsdelegatedCompletionto avoid the
reverse race (comment atCheckpointCaptureService.ts:131). Nothing protects this direction.isBlockingRun(Orchestrator.ts:537) treatswaitingas blocking. The delivery run is therefore
createdqueuedbehind its own parent. The capture already ran, so nothing moves the parent out of
waitingagain.
The finalize write in the event log is exactly the pre-capture snapshot plus the new cohort. In this
thread, 77 of 78 runs with a delegated-completion cohort settled normally. The one exception is the run
where the child finished 4.9 s after capturedAt. In a case one hour earlier, the gap was 10.5 s, the
run stayed completed, and the delivery run started immediately.
Line numbers refer to 3417bc38. The same code is present on main at 26fffe5a. Later fixes in this
area (#19355, #19631) do not change it.
Steps to reproduce
- On a V2 Claude thread, start an app-owned child with
delegate_taskand let it finish. - In a later turn, call
t3_thread_sendto that child thread. This creates an app-owned follow-up task
node on the current parent run. End the parent turn immediately. - Let the child answer while the parent's checkpoint capture is still in progress. The timing window lies
betweenfinalizeAppOwnedSubagentTask's read of the parent run and its write. A capture commit that lands
inside that window triggers the bug. - The parent run returns to
waitingwithcompletedAt: null. The delivery run and every later message
stayqueued, and the UI shows the turn as working.
This is timing-dependent. For a deterministic repro in a test, commit a checkpoint capture between that
read and that write.
Version
0.0.46-nightly.20261014.3105 (upstream 3417bc38). The server is a local rebuild with a small patch to
SQLite and storage cleanup (persistence/Sqlite.ts, storageCleanup.ts, nodeSqliteClient.ts). The
patch does not touch orchestration-v2.
Environment
Linux (Proxmox LXC), systemd user service, web client. Claude Code 2.1.301 with claude-opus-5-5.
Large statev2.sqlite (~20 GB).
Evidence
# orchestration_events, parent thread stream (UTC)
seq time event run state written
2480200 16:43:43.696 subagent.updated 1581 follow-up task node created by t3_thread_send
2480235 16:43:53.779 run.updated 1581 waiting completedAt=null checkpointId=null
2480244 16:43:54.232 run.updated (capture) 1581 completed completedAt=16:43:54.232 checkpointId=<set>
2480246 16:43:59.160 subagent.updated 1581 task completed, completionDelivery=claimed
2480247 16:43:59.160 run.updated (finalize) 1581 waiting completedAt=null checkpointId=null delegatedCompletion={open, gen 1}
2480252 16:44:02.501 run.created 1582 queued, queuePosition=1, userMessageId = cohort delivery messageId
# no later run.updated for 1581; every later run is created `queued`
# payload diff
2480244 -> 2480247: status completed->waiting, completedAt <ts>->null, checkpointId <id>->null, +delegatedCompletion
2480235 -> 2480247: only delegatedCompletion differs
# same pattern, settled correctly (1 h earlier, same thread)
1577 completed at 15:48:59.867; child finished 15:49:10.341 -> run.updated kept status=completed;
delivery run 1578 created `starting`, ran, cohort settled 15:49:19.886The provider turn of run 1581 is completed. The Claude session was idle-released 30 min later. The
server trace has nothing from this window because it runs at Warn level.
Related issues
- [BUG] V2 waiting run keeps showing Agent is working and offers Stop for finished work when checkpoint capture stalls #15124: a
waitingrun shows as working while checkpoint capture stalls. In this case capture finished, and the run was reverted afterwards. - Claude session idle-released while its run waits on a delegate_task child; Stop and Settle then fail permanently #15173: completion notices queue behind a wedged parent run. That run is wedged by an idle release
while it waits on a child, not by a stale write. - [Bug]: "Waiting on subagent" chip stuck after t3_thread_send steers into a running delegated child turn; Stop and task_cancel do nothing #19002: an app-owned
t3_thread_sendnode never settles. In this case the node settled, but the parent
run regressed.
Fix applied or workaround
Nothing has been applied; the thread is left wedged for inspection. Two expected workarounds, both not yet
verified here: Stop on the run, which should take the session-gone interrupt path because the provider turn
is completed, or a server restart.
Possible fixes: write only the delegatedCompletion change instead of a full-row snapshot. Alternatively,
do not let a run regress from completed with a checkpointId to a non-terminal status, for example with
a compare-and-set write like writeIfProviderThreadUnchanged from #19355.
Filed by
Claude Code (claude-opus-5-5). The investigation followed .github/triage/PLAYBOOK.md but was not started with t3 triage.
Activity
juliusmarminge commentedon Oct 15, 2026
Note
Grok responding on behalf of Julius.
Thanks for the detailed event-log trace. I checked the cited paths on current
main, and the race is real as described:CheckpointCaptureServicecommits a full-rowrun.updatedbuilt from the run it loaded before capture ({...runWithoutDelegatedCompletion, status: "completed", completedAt, checkpointId}).planDelegatedCompletionDeliveryreturns{...input.parentRun, delegatedCompletion}.finalizeAppOwnedSubagentTaskwrites that as a full-rowrun.updatedtoo.preserveRunRecordedFields(ProjectionStore.ts) only carries forwarddelegatedCompletionandrestartCancelledBackgroundWork. A stale snapshot can still movestatus,completedAtandcheckpointIdback.isBlockingRuntreatswaitingas blocking, so the delivery run queues behind the reverted parent.This is a different failure from #15124 (capture stalls), #15173 (idle release while waiting) and #19002 (follow-up node never settles). #19355 and #19631 don't touch this write path. I'm keeping it open as a bug.
A likely direction: stop the finalize write from regressing a terminal run, for example by writing only the
delegatedCompletionchange or by refusingcompleted/checkpointed → non-terminal transitions in the projection. For now, Stop on the stuck run or a server restart should get the thread moving again. We haven't verified either.Dropje97 commentedon Oct 15, 2026
Correction to "Fix applied or workaround": Stop does not recover this state. A server restart does.
Stop. Three Stop attempts on the wedged run were rejected with
OrchestrationV2DispatchCommandError: Run …:1581 is not interruptible.dispatchRunInterruptfinds noprovider turn to interrupt: the run is
waiting, its provider turn iscompleted, and there is nobackground work. The branch that handles a missing turn only covers
preparing,startingandrunning(Orchestrator.ts:9365), so awaitingrun falls through to the error at:9485. The linenumbers are the same on
3417bc38andmain. The web client does not surface the error, so Stopappears to do nothing.
t3_thread_interruptsends the same command and has the same limitation.Restart. The thread recovered without losing any queued messages:
queueHeld: true).queue.resume, the delivery run 1582 started, completed and got its checkpoint.{disposition: "open", delivery: null}. That is the same state as therun that did not race.
The only lasting effect is that run 1581 ends as
cancelled, withcheckpointIdstill null, insteadof
completed.A small addition to the possible fixes: let Stop settle a
waitingrun whose provider turn is alreadyterminal, so that this state can be recovered without a restart.