Skip to content

t3_background_wait accepts tasks owned by a foreground native subagent, which are killed when it ends its turn, so the subagent stalls silently #24956

Description

@hkarlsen06

What happened

look at the stalled subagent in this thread and figure out why it was allowed to happen. i also notice agents running commands in the background and monitoring them just show up as done in t3 code, including sub agents, so even though a sub agent is monitoring something and will wake up, t3 code shows everything as done

A native Claude subagent (an implementor in its own worktree) started a long test run in the background, called t3_background_wait on it, and ended its turn, saying "I'll pick up again when they report." It never woke up. The test run and its Monitor were killed in the same second the subagent ended. T3 showed the subagent and the thread as done. The parent passed the subagent's "I'll pick up again" on to the user, and nothing caught the stall until the user asked 16 minutes later.

Diagnosis

T3's instructions to Claude contradict each other inside a foreground native subagent, and t3_background_wait accepts a wait that can't happen.

  1. packages/provider-core/src/server/orchestrationInstructions.ts:9 tells Claude to run a native Agent "always with run_in_background:false". So the subagent is foreground. When a foreground subagent ends its turn, the subagent is finished, and Claude Code kills the background Bash tasks and Monitors it owns (task_updated {status: "killed"}, 2 ms before the subagent's own completed).
  2. CLAUDE_BACKGROUND_WAIT_INSTRUCTIONS (apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts:1307) says: "To wait for a background command or Monitor you started … call t3_background_wait with its task ID before you end your turn." Subagents see this too. Inside a foreground subagent, following it guarantees a stall.
  3. awaitBackgroundTasks (ClaudeAdapterV2.ts:10231) matches task IDs against the whole session's task list. That list includes tasks owned by a foreground subagent (parent_task_id set, owned_by_subagent: true). So the call returned matched for tasks that were about to be killed. Nothing tells the agent the wait is impossible.

Related: t3_background_wait also fails from a background native subagent in the same thread:

  • A subagent resumed with SendMessage was woken by its own background tasks while the parent was between turns. Its t3_background_wait call was rejected with parent_not_active (isLiveCallerShell, apps/server/src/mcp/OrchestratorMcpService.ts:218). The subagent was live, but T3 still had it as completed, because a resume doesn't start a new Agent tool call. The Lineage panel lists such subagents under "Previous agents" while they're running.
  • Passing a subagent's own task ID returns unmatched.

On the user's other complaint (threads show done while commands or Monitors run): this is by design (backgroundWorkKindHoldsCompletion in packages/shared/src/orchestrationV2PendingBackgroundWork.ts). Commands and Monitors hold Working only when marked awaited. In practice the mark rarely happens: 13 of this user's 200 Claude provider sessions ever called t3_background_wait, and every call in the thread investigated here came from a subagent.

Steps to reproduce

  1. On a Claude thread, ask for a task where the agent spawns a native Agent (foreground, as T3 instructs) whose work includes a command longer than 10 minutes. A UI test run works; Bash's 600 s limit moves it to the background.
  2. Inside the subagent, the command is backgrounded. The subagent calls t3_background_wait with the task ID (gets matched) and ends its turn.
  3. Observe: the background task and any Monitor get status: killed. The subagent completes with a "will pick up again" report. The thread shows done. No wake-up ever arrives.

Version

0.1.3-nightly.20261025.3669 (desktop app with local server; triage CLI was 0.1.2)

Environment

macOS 27.2 (arm64), Node 26.12.1, Claude Code 2.1.310 via claude-agent-sdk, claude-opus-5-5

Evidence

17:14:27.610 task_started   task_id=a9cfb0c3053a1c025 subagent_type=implementor is_backgrounded=false
17:34:59.785 task_started   task_id=bjovbiddl owned_by_subagent=true parent_task_id=a9cfb0c3053a1c025 "Mutation check …"
17:44:56.787 task_updated   bjovbiddl {is_backgrounded: true}   # Bash 600 s timeout moved it to background
17:45:54.270 task_started   task_id=bs384m83p (Monitor) owned_by_subagent=true parent_task_id=a9cfb0c3053a1c025
17:46:05.104 tool_use       mcp__t3-code__t3_background_wait {"taskIds":["bjovbiddl","bs384m83p"]}  (inside subagent)
17:46:05.163 tool_result    {"matched":["bjovbiddl","bs384m83p"],"unmatched":[]}
17:46:07.423 task_updated   bjovbiddl {status: "killed"}
17:46:07.452 task_updated   bs384m83p {status: "killed"}
17:46:07.454 task_updated   a9cfb0c3053a1c025 {status: "completed"}
17:46:07.456 hand-back:     "The mutation run and its monitor are both running in the background. I'll pick up again when they report."
18:02:02     parent SendMessage resumes the subagent after the user asks

# Same thread, background (SendMessage-resumed) subagent, parent between turns:
16:29:23.340 task_started   task_id=aada0d84ad95d00da is_backgrounded=true   # woken by its own task
16:29:41.587 tool_use       t3_background_wait {"taskIds":["bryakiqmh","bewduxibb"],"waiting":true}
16:29:41.624 tool_result    {"code":"parent_not_active","message":"The calling provider no longer owns an active thread run."}

Related issues

#16808 has the same parent_not_active rejection, for delegate_task from a background native subagent. This issue is about the foreground-subagent stall, which #16808 doesn't cover. #19926 (closed) added the Waiting state this relies on.

Possible fixes:

  • Reject or warn on t3_background_wait for tasks owned by a foreground subagent ("these die when you end your turn; wait in this turn or use delegate_task").
  • Tell subagents in the injected instructions never to end their turn on background work.
  • Flag a subagent that completes with killed owned tasks, so the parent and the UI don't report it as done.

Fix applied or workaround

Parent resumed the subagent with SendMessage; it finished. User-side: instructions now tell foreground subagents to wait in-turn and to send long verification jobs through delegate_task.

Filed by

Claude Code (claude-opus-5-5) via t3 triage

Activity

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

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions