`claude -p` with agent teams enabled: parked unnamed subagents get no second turn after their own background Bash or a fresh child completes
Environment
- Claude Code 2.1.251, and separately 2.1.250
- macOS, Darwin 24.6.0, arm64
- Headless main launched
--model opus. Every spawned agent pinnedmodel: "sonnet". In the own-background-Bash arm the producer is not an agent, it is a background Bash job launched by the parked subject. CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1- Plain text mode: no
--output-format, no--input-format, stdin closed (< /dev/null). Rawclaudebinary, no wrapper. - Not a clean configuration: these runs used my normal user settings, hooks, and MCP servers, and these non-default values:
CLAUDE_CODE_MAX_SUBAGENT_SPAWN_DEPTH=5,CLAUDE_ASYNC_AGENT_STALL_TIMEOUT_MS=900000,CLAUDE_STREAM_IDLE_TIMEOUT_MS=900000,CLAUDE_SUBAGENT_BG_SHELL_MAX_MS=10800000. They were held constant between the matched 2.1.250 headless and interactive runs.
Observed
0 wakes in 18 valid headless trials, across two versions and both wake channels. 15 of those ran with a second headless process alive; 3 were run in complete isolation and behave identically.
| wake channel | headless claude -p, 2.1.251 | headless claude -p, 2.1.250 | interactive control |
| --- | ---: | ---: | ---: |
| subject's own background Bash completes | 0 wakes / 6 trials (5 paired, 1 isolated) | 0 wakes / 5 trials | woke in 2s on 2.1.251 (main model unmatched), 2s on 2.1.250 (model-matched) |
| subject's fresh child completes | 0 wakes / 5 trials (3 paired, 2 isolated) | 0 wakes / 2 valid trials (3 attempted) | woke in 6s on 2.1.251 (unmatched), 4s on 2.1.250 (model-matched) |
Two trials are excluded and not scored as misses, because in each the producer never completed so there was nothing to deliver: one 2.1.250 fresh-child attempt whose producer wrote no marker, and one isolated own-Bash attempt whose background job was killed before its sleep finished (its task output file reads exactly [killed], and the main transcript contains no completion notification at all). Invalid is not the same as negative.
This is not just marker absence. I audited the subject's own transcript in all 8 concurrent 2.1.251 trials and all 3 isolated trials: every one ends at the assistant turn returning PARKED, with no later assistant or user record of any kind. The subjects were not re-invoked. On 2.1.250 I have the marker evidence and one archived subject transcript rather than all seven.
The miss also reproduced in a multi-round variant: after the producer completed, the main finished later Bash calls and wrote MAIN-TICK2 and MAIN-TICK3, and still no subject wake appeared. So a single long foreground keepalive is not necessary for the behavior.
Control limitation. The 2.1.250 pair is model-matched throughout and is the actual controlled surface comparison. The 2.1.251 interactive control ran in a session whose main was a different model from the headless main, so it shows the constructs can wake interactively on the current version but does not isolate execution surface from main-model choice.
What the session transcripts show
The two channels leave different signatures, and both reproduce in isolation.
Own background Bash. In the isolated run, the main-session JSONL contains exactly five queue-operation records, so this negative is exhaustive for that transcript rather than a partial search:
enqueueat04:30:23.047Z, task-ida889933e426628c37, the subject's own stop noticeenqueueat04:31:04.366Z, task-idb0lojdzn5, the background Bash job's completion, matchingSOURCE-DONEon diskremoveat04:32:20.762Z,reason: absorbed_mid_turn, task-ida889933e426628c37
Task-id b0lojdzn5, the one corresponding to the work the subject parked on, receives no remove record of any kind before the transcript ends.
Fresh child. Here the child's completion does get removed, but by the main rather than delivered to the subject. In the isolated long-window run: enqueue at 04:39:00.012Z for the child, then remove at 04:40:49.448Z with reason: absorbed_mid_turn, at the same instant the main's own stop notice is removed. The parked subject still has no second turn.
In the interactive controls the analogous item is removed with reason: delivered_to_agent and the subject wakes.
These are records as they appear in the session transcripts. They do not establish queue ownership, the notification's intended target, which consumer was responsible, or the stage at which delivery failed. I did not instrument the consumer.
Reproduction
One process, no wrapper. This reproduces on its own; I confirmed it with nothing else running.
Prerequisites: CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1 active, nested subagents allowed to depth 2, and Agent and Bash already permitted in print mode.
C3="$(mktemp -d)"; echo "$C3"
: > "$C3/arm1.log"
Write the prompt below to $C3/arm1.prompt with every <C3> replaced by that absolute path, since a spawned agent's shell does not inherit launcher variables. Then:
cd "$HOME"
nohup claude -p --model opus "$(cat "$C3/arm1.prompt")" > "$C3/arm1.out" 2> "$C3/arm1.err" < /dev/null &
DRILL C3 arm1 - headless main, own-background-Bash wake. Load no skills. Do not invoke any skill.
Follow these steps exactly, in order.
STEP 1 - Bash, exactly: printf 'MAIN-START %s\n' "$(date +%H:%M:%S)" >> "<C3>/arm1.log"
STEP 2 - Agent: spawn exactly ONE UNNAMED child, model "sonnet", subagent_type "general-purpose", passing NO isolation parameter, with this exact prompt:
DRILL C3 arm1 subject - You are disposable. Load no skills. Make exactly TWO tool calls, then end your turn. Tool call 1: Bash with run_in_background set to true, command exactly: sleep 45; printf 'SOURCE-DONE %s\n' "$(date +%H:%M:%S)" >> "<C3>/arm1.log" . Tool call 2: Bash, command exactly: printf 'SUBJECT-PARKED %s\n' "$(date +%H:%M:%S)" >> "<C3>/arm1.log" . Then immediately end your turn returning exactly: PARKED . Do not poll, do not read the log, do not spawn anything, do not send any message. IF you are re-invoked after your background command completes, make ONE Bash call with command exactly: printf 'SUBJECT-WOKE %s\n' "$(date +%H:%M:%S)" >> "<C3>/arm1.log" and then return exactly: WOKE
STEP 3 - Bash, exactly: printf 'SUBJECT-SPAWNED %s\n' "$(date +%H:%M:%S)" >> "<C3>/arm1.log"
STEP 4 - Bash keepalive, exactly this single command: for k in $(seq 1 24); do sleep 5; done
STEP 5 - Bash, exactly: printf 'MAIN-WINDOW-END %s\n' "$(date +%H:%M:%S)" >> "<C3>/arm1.log"
Then end your turn returning exactly: C3-ARM1-DONE
Expected arm1.log:
MAIN-START <t>
SUBJECT-SPAWNED <t>
SUBJECT-PARKED <t>
SOURCE-DONE <t>
SUBJECT-WOKE <t>
MAIN-WINDOW-END <t>
Actual, 2.1.251, isolated run, nothing else running:
MAIN-START 00:30:08
SUBJECT-SPAWNED 00:30:18
SUBJECT-PARKED 00:30:20
SOURCE-DONE 00:31:04
MAIN-WINDOW-END 00:32:24
Interactive control, same prompt text, same subject and producer models, same keepalive, 2.1.251:
SUBJECT-PARKED 22:56:15
SOURCE-DONE 22:56:59
SUBJECT-WOKE 22:57:01
A trial is scorable only if SUBJECT-PARKED precedes the producer marker and the producer marker precedes MAIN-WINDOW-END. Confirm a claimed second turn against the subject's transcript, not only the presence or absence of SUBJECT-WOKE. If the producer's marker never appears, discard the trial rather than counting it as a miss; I saw a background job killed before completion once, which produces no notification to deliver.
For the fresh-child channel, replace the subject's tool call 1 with an Agent spawn of one unnamed sonnet child whose whole prompt is: make exactly ONE Bash call for k in $(seq 1 9); do sleep 5; done, then exactly ONE more Bash call appending FRESH-CHILD-DONE <time> to the log, then end your turn returning exactly FRESH-DONE.
Scope
- Unnamed subagents only. Named teammates were not tested.
- Two wake channels only: the subject's own background Bash, and the subject's fresh child completing.
- macOS, on the non-clean configuration listed above. I did not run a clean-settings or clean-
HOMEarm. - 15 of the 18 trials ran with a second headless process alive; the 3 isolated trials were run with no other
claude -pprocess and behave the same, so concurrency is not required for the behavior. Interactive sessions belonging to my normal use were running on the machine throughout. CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMSwas held constant at 1. I have not run the flag-unset arm, so I cannot rule out that the flag matters.
Impact
In these trials the parked subject did not receive a second turn and so never performed its dependent step, while the headless main continued to the end of its window. For fire-and-park orchestration inside claude -p, that leaves the nested workflow stalled.
Related
#39632 (stream-json: background-task notification doesn't wake idle session (race in runAsyncAgentLifecycle), closed as not planned) is the nearest prior report and is about the headless main not receiving notifications. The symptom here is a parked nested subject getting no second turn while the main's own transcript records the completion. Whether they share an internal cause I cannot say from the outside. #20754 concerns several agents completing concurrently within one session; each main here had one producer, and the isolated trials had no concurrency at all.
This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗