Skip to content

Delegated task reports finished while its child thread still has running work #16603

Closed as not planned

Description

@PrivateVictories-Main

Area: apps/server

Steps to reproduce

  1. From a parent thread, delegate_task to a Codex child that starts a long-running shell command (a bot, a test watcher) and ends its turn without waiting for it.
  2. Read task_status, or look at the parent once it settles.

Expected: The task stays in progress (or reports waiting_for_children) while the child's command is still running, and the parent stays Waiting.

Actual: The task finalizes as completed / result_available and the parent can settle to Done, while the child thread still has a running command_execution item and the process is alive. delegatedTaskProgress (SubagentProjection.ts:207-259) checks runs, subagent entities and the provider roster, but not active turn items, and finalizeAppOwnedSubagent (Orchestrator.ts:8472-8488) finalizes on that result.

A related path: t3_thread_send into an already-finalized child (ThreadManagementService.sendToThread) starts new runs there, but the parent task stays completed and its later result is never delivered (OrchestratorMcpService.ts:1058-1077, Orchestrator.ts:8537-8546).

Evidence (0.0.46-nightly.20261004 state DB): 3 of 145 completed delegated tasks since Oct 1 finalized while their child had live command items (2 still running now, one confirmed alive with pgrep). 5 later runs across 3 finalized children.

Possible fix: include pendingBackgroundTurnItems({ turnItems, runs }) in delegatedTaskProgress (and load turn items in both callers), retrying finalization when those items end. For the send path, reject sends into finalized app-owned children or reopen task tracking.

Impact: Minor bug or occasional failure.

Activity

  1. juliusmarminge commented on Oct 6, 2026

    @juliusmarminge
    Member

    Note

    Grok responding on behalf of Julius.

    Confirmed on current main: an app-owned delegated task can finalize as result_available / completed while the child still has live background command_execution work.

    Main bug

    delegatedTaskProgress decides working / waiting_for_children / result_available from:

    • non-terminal work runs
    • active child subagents / pending completion delivery
    • provider-thread pendingBackgroundTasks

    It does not look at active turn items. finalizeAppOwnedSubagent loads the child without turnItems, calls that helper, and finalizes as soon as progress.state === "result_available".

    So a Codex child that starts a long-lived shell (countsForRun: false / turn ends without waiting) can look “done” to the parent while the process is still alive — matching the state-DB evidence in the report.

    The shared pendingBackgroundTurnItems helper already exists and is used elsewhere in the orchestrator (e.g. interrupt cleanup). It is the natural check to fold into delegatedTaskProgress (with callers loading turnItems).

    Secondary path (distinct)

    t3_thread_send into an already-finalized child can start new runs without reopening parent task tracking / delivering the later result. That overlaps open #13490 and open PR #15004 (follow-up result delivery). Those do not fix the premature-finalize case above.

    Loosely related: #13625 (completion sound while background work runs), #15993 (stale background banner).

    Fix direction: include pendingBackgroundTurnItems({ turnItems, runs }) in delegatedTaskProgress (and load turn items in both finalize / status callers); keep finalization deferred until those items end. Separately, for send-into-finalized-child, either reject or reopen tracking (coordinate with #15004).

    Labeling bug + via-triage.

  2. added
    bugSomething is broken or behaving incorrectly.
    via-triageFiled through npx t3 triage
    on Oct 6, 2026
  3. emilvakhitov commented on Oct 7, 2026

    @emilvakhitov

    Additional user-observed scenario with nested subagents:

    Root thread: completed
    `-- Subagent A: completed
        `-- Subagent B: still running
    

    From the original root thread, the remaining descendant activity is not visible, so the delegated workflow appears finished while B continues working.

    This is an observation, not an independently reproduced case. It is unclear whether it has the same underlying cause as the command_execution gap described above.

    Consolidating the related report from #16902 here to avoid parallel tracking.

  4. jamesvillarrubia commented on Oct 20, 2026

    @jamesvillarrubia
    Contributor

    Same outcome with a Claude child, on 2026-10-20. The open work here is a native background subagent, not a shell command. The machine's installed runtime is 0.1.1-nightly.20261019.3373.

    1. 18:23:32Z: the parent sends the child a follow-up with t3_thread_send. T3 tracks it as a delegated task.
    2. 18:24:11Z: the child launches a background subagent. T3 writes a record for it on the child in orchestration_v2_projection_subagents (origin: provider_native, status: running).
    3. 18:31:45Z: the child's turn ends with "I'm waiting on two things in the background: the price-research child and a watcher for its output file."
    4. 18:31:45Z: T3 finalizes the delegated task and delivers its result to the parent. The record for the child's subagent still reads status: running.
    5. 18:35:59Z: the child's subagent completes.

    The issue body says delegatedTaskProgress checks subagent entities. This record read status: running when T3 finalized the task. The parent agent learned the work was unfinished only from the child's final text, and sent another follow-up at 18:31:57Z.

  5. juliusmarminge commented on Oct 22, 2026

    @juliusmarminge
    Member

    Triage read the report and follow-up, scanned all 949 open issues, and inspected current main at a12d6ed942. No earlier duplicate identified; #21837 covers a related follow-up lifecycle. Runtime reproduction was not attempted. This requires maintainer review under the automated triage restriction on MCP tool-surface changes: the requested behavior changes delegated-task completion returned by task_status and follow-up handling through t3_thread_send. No code changes or PR.

  6. seankoji commented on Nov 2, 2026

    @seankoji

    Additional observed case on 2 November 2026: a delegated implementation agent displays Done while its required asynchronous review delegate is still running. Adding here because #16902 explicitly consolidated this nested-descendant visibility case into #16603.

    Steps observed

    1. An implementation coordinator launches a required asynchronous review with delegate_task.
    2. The host result instructs it: "Completion wakes this thread, so end your turn. Do not wait on it or poll it."
    3. The coordinator ends its turn awaiting that review.
    4. Open Lineage while the review remains active.

    These steps describe the supplied live-session evidence. No fresh isolated reproduction was run.

    Expected behavior

    The aggregate workflow status remains pending, for example Waiting for review/delegates, while a required descendant is active. The parent agent's completed turn can remain visible separately. Active required descendants should remain discoverable from ancestors.

    Observable acceptance:

    • Aggregate status remains pending during required child activity.
    • It transitions correctly when that child completes, fails, or is cancelled.
    • A terminal parent-turn status remains distinguishable from completion of the delegated workflow.

    Actual behavior and evidence

    • A screenshot from the implementation conversation shows "Lineage · 1 running", the implementation parent marked Done, and its required Opus review still running at about 16 minutes elapsed.
    • A separate root-conversation screenshot shows only direct previous agents. This supports a related nested-descendant discoverability concern; it does not establish the same underlying cause as the Done ambiguity.
    • The main coordinator's read-only T3 thread inspection confirmed an active claudeAgent / claude-opus-5-5 run with a parent relation to the ended implementation delegate. Completed command activity was recorded at 14:01 AEDT, UTC+11, on 2 November 2026. That command completion is not evidence that the review itself completed.
    • The required source milestone remained pending at observation time.

    This is evidence of user-visible pending-work/status ambiguity. It does not establish premature backend task finalization, a reducer defect, lost tasks, cancelled work, or failed completion delivery. Root cause is unknown.

    Environment and version limits

    T3 Code on Linux, with nested T3 asynchronous delegation and a Claude Agent Opus 5.5 reviewer. Read-only installed metadata identifies CLI runtime 0.1.4-nightly.20261028.3814 and desktop package 0.1.3-nightly.20261023.3566. These are installed versions, not a verified binding of the screenshot or active server to either version; the exact affected running build is unknown.

    Impact and workaround

    Users can mistake an ended parent turn for finished work and lose visibility of a required pending review. No verified UI workaround. The supplied evidence distinguishes parent-turn completion from outstanding review work; no claim is made that execution stopped.

    Related reports and scope

    #27126 concerns an async delegate hint stopping otherwise independent work. Here the review is required and ending the turn is expected; this report concerns the displayed aggregate state. #27700 concerns native Claude subagent background commands/monitors. This case uses a required T3 asynchronous review delegate, without assuming the same cause.

    Sanitized text only. Private screenshots, transcripts, task identifiers, repository details and machine paths are intentionally omitted. No implementation, task cancellation, restart, or monitoring was performed for this report.

  7. vilmerfrost commented on Nov 2, 2026

    @vilmerfrost

    Same bug with a Claude child, on 0.1.7-nightly.20261102.4066 (macOS, Claude Code 2.1.316, Claude Opus 5.5). The report above uses a Codex child.

    Steps observed

    1. A Claude parent starts a child with delegate_task, isolation: "worktree", mode: "async".
    2. The child starts a long script with Bash run_in_background and ends its turn with the message "Waiting for background task <id>, the final evidence run."
    3. The parent calls task_status.

    Result

    While the script was still running, task_status returned:

    {
      "status": "completed",
      "workState": "result_available",
      "hasPendingChildRuns": false,
      "latestTerminalSummary": "Expect: about 25 minutes and little plan usage. Waiting for background task `<id>`, the final evidence run."
    }

    At the same moment, on the machine, the script's process and the child's claude process were alive, and the child had not pushed its branch.

    The parent was woken with a [completed] message whose whole text was the waiting line. The same thing happens on every turn the child ends while it waits: in this session four workers sent about 15 [completed] messages between them that were not final results. Each background completion starts a new child run through a provider continuation, and that run delivers another [completed].

    Impact

    A parent has no reliable way to tell a finished child from a waiting one. Here both the parent agent and the person watching the app read a running worker as done. The parent has to inspect the child's checkpoint or processes to find out.

    Not tested

    I did not check whether calling t3_background_wait inside the child changes this. I did not run a patched build.

    Related: #27700 covers the same state for native Claude subagents, and anthropics/claude-code#92952 covers the completed status it comes from.

    Written with Claude Opus 5.5 in T3 Code.

  8. hishamank commented on Nov 2, 2026

    @hishamank

    Same nested case on v0.1.6 (Linux desktop app), with counts from one long-running session. All delegates were started with delegate_task. The parents were Claude Opus 5.5, and the children were a mix of Claude and an ACP provider.

    Observed

    root: latest run completed, no active run, 72/72 subagent records completed
    ├─ orchestrator A: latest run completed → review delegate: running
    ├─ orchestrator B: latest run completed → build delegate: running; review delegate: running
    └─ orchestrator C: latest run completed → mapping delegate: running
    

    From the code, the root row should read as finished: its status comes from its own runs and its own unfinished records, and nothing here is active. I read this from the state database and the source, and did not capture the sidebar itself.

    Two paths, both in the state database

    1. A follow-up closes while its nested work runs. In finalizeAppOwnedSubagent, only the original task is held back by progress.state. Example from orchestrator C:

      12:09:07.563  orchestrator opens a nested delegate
      12:09:17.934  orchestrator's run ends
      12:09:18.449  root's follow-up record closes as completed; the nested delegate is still running
      
    2. A run starts with no root record open. Orchestrator A's last root record closed at 10:31:31. A nested completion woke it at 12:06:13, and it opened a new delegate at 12:06:36. The root had no open record for any of this.

    Orchestrator Root records closed while a nested record was open Runs with no open root record
    A 26 of 29 110 of 166
    B 10 of 13 49 of 67
    C 17 of 21 45 of 96

    These compare timestamps across each orchestrator's own records. They count overlaps, and don't show that each overlap was visible in the UI.

    Related work

    Path 1 looks like what #21877 addresses, and path 2 looks like what #28462 addresses. I have not run a build with either PR, so I can't say whether the two together keep the root in a pending state for this whole tree.

    Written with Claude Opus 5.5 in T3 Code.

  9. jamesdchen commented on Nov 2, 2026

    @jamesdchen

    Another case with a Claude child on v0.1.6 on Windows, using mode: "async", isolation: "shared". The child's own final message says it isn't finished, but the task and the thread read as done.

    What happened. A child started a background Bash watcher, then ended its turn with this message:

    W6 isn't finished: nothing has been submitted yet... Background watcher bqkvktnji checks both local files every 2 minutes, for up to 2 hours, and wakes me when the gate opens, the suite fails or it times out.

    The parent received Delegated task ... [completed] with that text. t3_thread_read shows the thread as status: completed, activeRunId: null, runCount: 1, and t3_thread_list with statuses: [running, starting, queued, waiting] doesn't list it, while the watcher it describes is meant to still be alive.

    Why the status can't be relied on, in the same session. Two sibling children showed the same completed state after similar turns and then woke up later and did real work:

    • One submitted 30 cluster jobs under a later provider-continuation task ID, after its original task had reported completed.
    • Another woke up several more times on its own, reported state changes and resubmitted a failed job.

    So completed can mean either "finished" or "waiting on my own background work", and the parent can only tell by reading the child's prose.

    The practical cost. A parent seeing completed on unfinished work starts a replacement, which then risks duplicating the original if it wakes. I had to start a replacement for this child with no way to confirm the first wouldn't resume. For long fan-outs this also produces many [completed] notifications carrying interim text such as "Waiting for the retry to report back."

    Expected. As this issue describes: waiting_for_children or similar while a child has live background work, including Claude run_in_background commands, and a final completed only when it has really ended.

  10. dpearson2699 commented on Nov 6, 2026

    @dpearson2699

    Same bug with a Claude child on 0.1.6-nightly.20261031.3977 (macOS 27.0.1 arm64, desktop app on a local server). This adds what it does to the parent's Lineage panel.

    Observed

    1. A Claude Opus 5.5 parent started a Claude Opus 5.5 child with delegate_task (isolation: "worktree").
    2. The child started long test runs with Bash run_in_background, called t3_background_wait on them, and ended its turn. It woke and carried on each time a run finished.
    3. In orchestration_v2_projection_subagents, every task record for the child read completed / delivered, one per turn. Each was written while the child's background runs were still pending; four of its test processes were alive on the machine at the time.

    What the parent's Lineage panel showed

    While the child was working, the parent's thread details panel showed no active agent. Lineage showed only a collapsed "Previous agents · 1 failed · 194", with the live child folded in among the finished ones. The child's own panel listed the parent correctly.

    From the source (checked against main at c52df24), this follows from the task status:

    • Between turns the child has no live run, so its activityRunStatus is null. The subagent edge's status in deriveThreadRelationshipGraph (packages/client-runtime/src/state/threadRelationships.ts) then falls back to the child's completed status, because the subagent record is no longer active.
    • ThreadRelationshipsPanel (apps/web/src/components/chat/ThreadRelationshipsControl.tsx, the groupBy that returns "previous") puts every subagent edge with a settled status under "Previous agents".

    So if the subagent record stayed running while the child waits on its own background work, the edge would take that status and the child would stay in the active list. The Lineage symptom should not need a separate client fix.

    Observed in a live session, not reproduced in isolation.

    Filed by Claude Code (claude-opus-5-5) inside T3 Code, following .github/triage/PLAYBOOK.md.

  11. 8 remaining items

  12. Possemaster commented on Nov 10, 2026

    @Possemaster

    Still reproducible on t3 v0.1.8 (Claude Code 2.1.322 parent and child), via the follow-up path described above:

     

    1. The parent sent a follow-up to a finished delegate_task child with t3_thread_send. The answer was delivery: "started" with a new taskId.
    2. The child (run 4) started a long UI-review command in the background and ended its turn with "Waiting for the run's completion notification."
    3. The parent immediately got a completion for the new taskId, with that run-4 text as the result.
    4. The background command exited, and the child woke as run 5 (08:44:27 → 08:46:09 UTC). Run 5 wrote the real report and a commit. That result was never delivered to the parent.
    5. t3_thread_wait(threadId, untilDelegatedWorkSettled: true), called while the background command was still running, also returned resultRun = run 4 (completed, not timed out) before run 5 existed. Only a second t3_thread_wait on run 5's id, then t3_thread_read, got the real report.
       
      So the parent sees a progress note as the final result, and untilDelegatedWorkSettled does not wait for the pending background-exit wake either (the same gap as [Bug]: Original delegate_task task finalizes on a stale result in the gap before the child's background-exit wake turn #24297, on the follow-up task).
  13. juliusmarminge commented on Nov 12, 2026

    @juliusmarminge
    Member

    I tried to reproduce this on the current build and could not.

    Commit 1e438c8 (origin/main). Not run in the live app: no provider on this VM reports server-tracked background commands (Codex and Claude need first-party OAuth, which is unavailable here). Reproduced instead in the real orchestrator with its in-memory event sink. I added the test 'subagent follow-ups > keeps a follow-up running while its child awaits a background command' to apps/server/src/orchestration-v2/DelegatedCompletionDelivery.test.ts. It reuses the startSelfQueuedFollowup fixture: the parent sends a follow-up to a finished delegated child, the follow-up run starts, the child's provider thread records pendingBackgroundTasks [{kind: 'command', awaited: true}], and the follow-up run completes with the text 'Waiting on the test run.'. On main the parent's follow-up subagent task settles with result 'Waiting on the test run.' while the awaited command is still pending. Run with: cd apps/server && vp test run src/orchestration-v2/DelegatedCompletionDelivery.test.ts -t follow-up. Result: 1 failed, 23 passed.

    Closing as not planned. If it still happens for you, reply with the exact steps and the version you are on and it will be looked at again.

  14. sammyhass commented on Nov 12, 2026

    @sammyhass

    Still happening on 0.1.9 (desktop app on a local server, macOS arm64, Claude Opus 5.5 parent and children). A unit test reproduces it on current main, and open PR #34958 fixes it in that test.

    This case differs from the earlier no-repro test in two ways:

    1. The task that finished early was a t3_thread_send follow-up, not the original delegate_task.
    2. The pending work was a Claude native background subagent (provider_native), not a background command.

    Observed (one child, 12 Nov, UTC)

    Root thread                                  idle
    └─ Child A (delegate_task)                   12 app_owned task records on the root, all completed/delivered
       │   original task 13:54:02 → 14:49:04; the other 11 are thread-send follow-ups / provider continuations
       ├─ native subagent B1 (background Agent)  running, started 15:10:30, still updating at 16:21
       └─ native subagent B2 (background Agent)  running, started 16:13:58, still updating at 16:21
    
    1. Root sends A a follow-up with t3_thread_send (task …thread-send:a84b1471…, run 12 starts 16:13:22).
    2. During run 12, A starts B2 with Claude's background Agent tool at 16:13:58, then ends its turn 8s later, waiting for B2's wake.
    3. At 16:14:06 the follow-up task becomes completed / delivered on the root, while B1 and B2 are running in orchestration_v2_projection_subagents on A's thread.
    4. The root's subagents view lists only its direct children, and all of them are finished. Nothing running shows from the root, although two grandchildren are working.

    Expected: the follow-up stays waiting_for_children until the child's subagents settle, and the root shows delegated work still active below it.

    Cause

    In finalizeAppOwnedSubagent (apps/server/src/orchestration-v2/Orchestrator.ts, same on v0.1.9 and main), delegatedTaskProgress only gates the original task:

    (task.id === childControls.thread.forkedFrom.nodeId &&
      (cancelled ? progress.state === "working" : progress.state !== "result_available"))

    Every follow-up skips that check. finalizeAppOwnedSubagentTask then finalizes it from its own run's outcome, without looking at subagents that run started.

    Test results

    I added a test to DelegatedCompletionDelivery.test.ts under subagent follow-ups. A finished delegated child gets a t3_thread_send follow-up. During that follow-up run the child starts a provider_native subagent (status: running), then ends its turn with an interim reply.

    Build Result
    main @ fd8e386f Fails. the follow-up task settled on its handoff turn while the child's nested subagent was running: expected 'Waiting on the background subagent.' to equal null. The fixture check that the nested subagent is still running passes first.
    #34958 @ ac7bb5f2 Passes. 25/25 tests matching -t "follow-up".

    So #34958 also covers Claude native background subagents, not only the app_owned subagent its own test uses. I haven't run the live app on that branch.

    Run: cd apps/server && vp test run src/orchestration-v2/DelegatedCompletionDelivery.test.ts -t "follow-up"

    Test (insert before "waits for a run the follow-up's child queued on its own thread")
        it.effect("keeps a follow-up running while its child's nested subagent runs", () =>
          Effect.gen(function* () {
            const orchestrator = yield* Orchestrator.OrchestratorV2;
            const eventSink = yield* EventSink.EventSinkV2;
            const requests = yield* ProviderContinuationRequests.ProviderContinuationRequests;
            const now = yield* DateTime.now;
            const name = "nested-followup";
            const parentThreadId = ThreadId.make(`thread:${name}-parent`);
            const projectId = ProjectId.make(`project:${name}`);
            const parentRunId = RunId.make(`run:${name}-parent`);
            const rootNodeId = NodeId.make(`node:${name}-parent`);
            yield* seedParentWithTerminalTask({
              threadId: parentThreadId,
              projectId,
              runId: parentRunId,
              rootNodeId,
              taskId: NodeId.make(`node:${name}-seed`),
              deliveryState: "acknowledged",
              now,
            });
            const child = yield* seedRestartCancelledChild({
              parentThreadId,
              projectId,
              parentRunId,
              rootNodeId,
              name: `${name}-child`,
              completionWake: "always",
              continuationPending: false,
              runStatus: "completed",
              now,
            });
            yield* orchestrator.recoverDelegatedTasks;
            let firstWake = yield* requests.take;
            while (firstWake.threadId !== parentThreadId) firstWake = yield* requests.take;
            yield* orchestrator.dispatch({
              type: "delegated_task.completion-delivery.acknowledge",
              commandId: CommandId.make(`command:${name}-ack`),
              parentThreadId,
              taskId: child.taskId,
              observedByRunId: parentRunId,
            });
            const parentRun = (yield* orchestrator.getThreadProjection(parentThreadId)).runs.find(
              (run) => run.id === parentRunId,
            )!;
            yield* eventSink.write({
              commandId: reconcileCommandId(`${name}-parent-finished`),
              events: [
                {
                  id: EventId.make(`event:${name}-parent-finished`),
                  type: "run.updated",
                  threadId: parentThreadId,
                  runId: parentRunId,
                  occurredAt: now,
                  payload: { ...parentRun, status: "completed", completedAt: now },
                },
              ],
            });
            // The parent sends the finished child a follow-up (t3_thread_send).
            const followupMessageId = MessageId.make(`message:${name}`);
            yield* orchestrator.dispatch({
              type: "message.dispatch",
              createdBy: "agent",
              creationSource: "mcp",
              commandId: CommandId.make(`command:${name}`),
              threadId: child.childThreadId,
              senderThreadId: parentThreadId,
              messageId: followupMessageId,
              text: "Build the codemod.",
              attachments: [],
              dispatchMode: { type: "start_immediately" },
            });
            const queuedRun = (yield* orchestrator.getThreadProjection(child.childThreadId)).runs.find(
              (row) => row.userMessageId === followupMessageId,
            )!;
            const followupTaskId = queuedRun.delegatedTaskId;
            assert.isDefined(followupTaskId);
            const followupRun = { ...queuedRun, status: "running" as const, startedAt: now };
            // During the follow-up run, the child starts a Claude background subagent.
            const nested = {
              id: NodeId.make(`node:${name}-nested`),
              threadId: child.childThreadId,
              runId: followupRun.id,
              parentNodeId: NodeId.make(`node:${name}-child-root`),
              origin: "provider_native" as const,
              createdBy: "agent" as const,
              driver,
              providerInstanceId: modelSelection.instanceId,
              providerThreadId: null,
              childThreadId: null,
              nativeTaskRef: null,
              prompt: "Nested work",
              title: null,
              model: null,
              status: "running" as const,
              result: null,
              startedAt: now,
              completedAt: null,
              updatedAt: now,
            };
            yield* eventSink.write({
              commandId: reconcileCommandId(`${name}-running`),
              events: [
                {
                  id: EventId.make(`event:${name}-running`),
                  type: "run.updated",
                  threadId: child.childThreadId,
                  runId: followupRun.id,
                  occurredAt: now,
                  payload: followupRun,
                },
                {
                  id: EventId.make(`event:${name}-nested-open`),
                  type: "subagent.updated",
                  threadId: child.childThreadId,
                  runId: followupRun.id,
                  nodeId: nested.id,
                  driver,
                  providerInstanceId: modelSelection.instanceId,
                  occurredAt: now,
                  payload: nested,
                },
              ],
            });
            // The child ends its turn to wait for the nested subagent's wake.
            yield* eventSink.write({
              commandId: reconcileCommandId(`${name}-handoff`),
              events: [
                {
                  id: EventId.make(`event:${name}-handoff:result`),
                  type: "message.updated",
                  threadId: child.childThreadId,
                  runId: followupRun.id,
                  occurredAt: now,
                  payload: {
                    id: MessageId.make(`message:${name}-handoff:result`),
                    threadId: child.childThreadId,
                    runId: followupRun.id,
                    nodeId: null,
                    role: "assistant",
                    text: "Waiting on the background subagent.",
                    attachments: [],
                    streaming: false,
                    createdBy: "agent",
                    creationSource: "server",
                    createdAt: now,
                    updatedAt: now,
                  },
                },
                {
                  id: EventId.make(`event:${name}-handoff:finished`),
                  type: "run.updated",
                  threadId: child.childThreadId,
                  runId: followupRun.id,
                  occurredAt: now,
                  payload: { ...followupRun, status: "completed", startedAt: now, completedAt: now },
                },
              ],
            });
            yield* orchestrator.recoverDelegatedTasks;
            const childProjection = yield* orchestrator.getThreadProjection(child.childThreadId);
            assert.equal(
              childProjection.subagents.find((task) => task.id === nested.id)?.status,
              "running",
              "fixture: the nested subagent is still running on the child",
            );
            const followup = (yield* orchestrator.getThreadProjection(parentThreadId)).subagents.find(
              (task) => task.id === followupTaskId,
            )!;
            assert.isNull(
              followup.result,
              "the follow-up task settled on its handoff turn while the child's nested subagent was running",
            );
            assert.notEqual(followup.status, "completed");
          }),
        );

    Open question

    Claude starts the child's wake after a native subagent finishes, not a server delegatedCompletion, so #34958's delegatedTaskRuns won't attach that wake to the follow-up. In this database those wakes appear on the parent as separate provider-continuation tasks, so the final reply probably still arrives, as its own task. Not verified.

    Related: #16902 (same Root → A → B shape, merged into this one), #29128.

    Triaged with Claude Code (Claude Opus 5.5) via T3 Code triage.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething is broken or behaving incorrectly.via-triageFiled through npx t3 triage

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions