Repository navigation
[Bug]: Delegated Codex child completes, but its subagent node stays Running and the parent is never woken #17512
Description
Activity
Note
Grok responding on behalf of Julius.
Thanks for the detailed report and the database counts. I read the code on
main(ec80933). I didn't reproduce this, so treat the following as a likely lead rather than a diagnosis.How the row should move to
completed- The
orchestration_v2_projection_subagentsrow is an upsert keyed onsubagent_id(the parent's task node), driven bysubagent.updatedon the parent thread: ProjectionStore.ts#L2030-L2096. - For app-owned children, the only live writer of the terminal row is
finalizeAppOwnedSubagent, which runs from the terminalrun.updatedlistener on the child thread: Orchestrator.ts#L10630-L10697. It writes the terminalsubagent.updated, the result transfer, and the wake together: Orchestrator.ts#L9766-L9871.
Where it can return without writing anything
finalizeAppOwnedSubagenthas several earlyreturns before that write: Orchestrator.ts#L9586-L9665. Your row (updated_atequal to creation time, nocompleted_at) fits one of them firing. The most plausible one seems to be L9600-L9601: it only proceeds whendelegatedTaskProgressreportsresult_available. That function reportswaiting_for_childrenif the child thread has any subagent row still active, a child task whose completion delivery ispending/claimed, or a provider thread withpendingBackgroundTasks: subagentProjection.ts#L237-L256. On acompletedrun, leftover run-owned subagents are not cascaded to a terminal status (the cascade covers onlyinterrupted/failed/cancelled): RunExecutionService.ts#L162-L165.If that check is what fired, nothing re-runs finalize later while the server stays up. The listener reacts only to terminal
run.updatedevents, so a nested item that settles a few seconds after the run (your trace shows a write at13:54:14, after the run completed at13:54:01) does not trigger it again. The catch-up passrecoverDelegatedTasksruns only at startup: Orchestrator.ts#L10704-L10731, ProjectionStore.ts#L3569-L3593. Any error inside finalize is also only logged as a warning ("Failed to react to terminal V2 run"), with no retry.The overlapping parent runs probably don't explain the stuck row. Finalize looks up the parent run by
task.runIdand writes the terminal status whatever that run's state is. The parent run's state only affects whether a wake is offered: Orchestrator.ts#L9403-L9436.Why Stop doesn't clear it: stopping the parent run marks the task's
completionDeliveryasdisposedbut copies itsstatusunchanged: Orchestrator.ts#L2115-L2139. The child has no live run left to interrupt, so no new terminalrun.updatedarrives to re-run finalize.Related: #15888 (there the Grok child run itself stays non-terminal), #16355 and #17154 (nested subagents left
running, task stuck inwaiting_for_children), which may share this root cause if the Codex child had a nested item open. Open PR #15201 changestask_statusreads, startup recovery, and pending-background-work derivation. From its diff it doesn't seem to add a live re-check offinalizeAppOwnedSubagent, so it may not cover this case on its own.Useful next data, if you still have the stuck case:
- What
task_statusreturns for that task, in particularworkState. - In the child thread: any
orchestration_v2_projection_subagentsrows stillrunning, andpendingBackgroundTaskson its provider thread. What wrote the13:54:14event? - Any
Failed to react to terminal V2 runwarning around13:54:01. - Whether restarting the server settles the node and wakes the parent. The startup recovery pass would run finalize again.
- The
- addedbugSomething is broken or behaving incorrectly.Something is broken or behaving incorrectly.via-triageFiled through npx t3 triageFiled through npx t3 triage
on Oct 9, 2026 Another case on the same nightly (
0.0.46-nightly.20261009.2861), this time with a Claude child. It includes the data asked for aboveSetup
- A Claude parent runs
delegate_task(async) to a Claude child - The child starts native background work and ends its turn
- A background-task notification queues a wake run 25 ms before run 1 is recorded complete
- The wake run finishes in about 0.15 s with no reply
- The parent then calls
task_cancel
Expected
The Lineage row shows the child as finished, and Stop is not offered
Actual
- The header reads "Lineage · 4 running" and counts this child
- Its timer counts from the task's original start (1.3h and rising)
- Stop subagent does nothing
Timeline (UTC)
Time Event 16:05:05.954 Run 2 (wake) requested 16:05:05.979 Run 1 completed 16:05:09.090 Run 2 completed 16:05:20 First task_cancelreturnscancel_requestedwith no run active. It recordsrun_interrupt_requeston the already completed run 216:22 task_statusreturns the values below16:22 Second task_cancelreturnscompleted.t3_thread_interruptreturnsno_active_runThe child thread itself reports
completedwith no active runtask_statusat 16:22status: completed workState: result_available hasPendingChildRuns: false resultContextTransferId: nullHow it fits the lead above
- At run 1's end, run 2 was already queued, so finalize skipped
- At run 2's end, finalize skipped again. The task was still waiting on background work, inferred from step 3
- The first
task_cancelreturnedcancel_requestedwith no active run, which only happens inwaiting_for_children - The cancel cleared that work but produced no new terminal
run.updated, so finalize never re-ran - The app-owned row stayed
running. The Lineage edge falls back to that row when the child has no live run, so the count, the timer and the Stop button all follow it
Also observed
- Settling the child thread does not clear the row. It still showed Running 1h 31m about 12 minutes after settling, while the server still reported the thread as
completedand settled
Not tried
- Whether a server restart's recovery pass settles it
- A Claude parent runs
Before submitting
Area
apps/server
Summary
A Claude parent starts a Codex child with
delegate_task(async). The child finishes normally: its run and its provider turn arecompletedand its final answer is visible in the child thread. But the app-owned subagent node for that task staysrunningforever. The parent's Lineage card shows the child as Running with the timer still counting, the parent is never woken with the result, and the card can't be stopped. The only way out is to tell the parent by hand that the child is done so it reads the child thread.This has happened 3–4 times for me. It's different from #15888: there the child run itself never reaches
completed. Here the child run and turn do reachcompleted; only the subagent node and the completion delivery are lost.Environment
0.0.46-nightly.20261009.2861, macOS 27.0, local serverclaude-opus-5-5)delegate_taskasync →codex/gpt-6.1-sol,reasoningEffort: ultra,serviceTier: priority, full-accessSteps to reproduce
No reliable single-run repro. In a long Claude thread that delegates many Codex reviews:
delegate_task(async) with a Codex target and ends its turn to wait.Expected behavior
When the child run completes, the subagent node becomes
completed,task_statusreturns the result, and the parent gets a completion wake, as for the other children.Actual behavior
Live case from today (still stuck while writing this). Times in UTC:
thread:delegated-task:command%3Amcp%3A0a10d597-…%3Adelegate-task%3Asources-stage3a-code-r1ordinal:1completed, requested13:49:00.312, completed13:54:01.723completed,13:49:01.109→13:54:01.000orchestration_v2_projection_subagentsrow for this nodeorigin=app_owned,status=running,completed_at=NULL,updated_at=13:49:00.307(never updated after creation)81332a00-…, owning runordinal:30(13:40:41→13:53:08)The server trace for the child shows the final
EventSink.write/EventSink.writeIfRunCurrentat13:54:01and13:54:14, allSuccess, with no error after that. The only failures for this child are twoProjectionStoreThreadNotFoundErrorat creation (13:49:00), but the same error also appears for children that completed normally (…sources-stage2-3a-code-r2), so it's probably unrelated.In the same thread, every other delegated child's node got
completed_atwithin milliseconds of a new parent run starting (the completion wake). For example,…sources-stage2-3a-code-r2completed at14:18:08.631, and parent runordinal:38was requested at14:18:08.701. Forstage3a-code-r1there is no such pair.Across my whole
statev2.sqlite: 67 app-owned Codex children. 64 have nodecompleted+ runcompleted, 1 isinterrupted/interrupted, and 2 have noderunningwhile the child run iscompleted:…delegate-task%3Akdl-docs-stage2-review-r1(parentmcp:d69ea091-…, owner runordinal:46): child run completed12:29:31.409, node stillrunning.One thing I noticed: when
stage3a-code-r1was launched, the parent had overlapping runs. The owning run 30 was still open, run 31 was being cancelled (finished13:49:04.990), and run 32 was running. The second case didn't have that overlap, so I'm not sure it matters.Nothing in the UI recovers this. Stop doesn't clear the card, and the parent keeps waiting until I message it.
Workaround
Send the parent a message saying the child finished, so it reads the child thread with
t3_thread_read.