Repository navigation
Ad-hoc spawn_agent subagents are never announced with native subagent sessions: their output is dropped and the spawn tool call stays in_progress #592
Description
Summary
With native subagent sessions negotiated, a subagent started by an ad-hoc spawn_agent call never gets subagent_spawned. The client receives no child session and no child output, and the spawnAgent tool call stays in_progress for the rest of the session. If the child needs an approval, the turn hangs.
This is the bug that #482 described and fixed for 1.7.0. That PR was closed without merge, and the code path is unchanged on current main (4583cb3978276c2b7ea87fcfa5ae66e1e9817f13). There is no open issue for it, so I am filing one with a reproduction on current releases.
Environment
- codex-acp 2.1.1 with Codex CLI 0.159.3, and codex-acp 2.1.0 with Codex CLI 0.160.1. Both show the same behavior.
- macOS (arm64), Node 22.23.1.
- Model
gpt-5.6-luna,reasoning_effortlow, featuremulti_agentenabled (the default). - Minimal ACP client over stdio.
clientCapabilities:fsread/writefalse,terminal: false,elicitation: { form: {} }. - Native subagent sessions are negotiated through
_meta.jetbrains.air: { version: 1, capabilities: ["nativeSubagentSessions"] }. The canonicalclientCapabilities.subagents: {}does not reach the adapter: the bundled@agentclientprotocol/sdk1.5.1 parsesclientCapabilitieswith a strictz.objectand drops the field. I can file that separately if useful.
Reproduction
-
initializewith the capabilities above, thensession/newin an empty directory that containsb.txt. -
session/set_config_option:model=gpt-5.6-luna,reasoning_effort=low,mode=agent. -
session/prompt:Use your multi-agent tools (spawn_agent) to start exactly two sub-agents in parallel, then wait for both. Sub-agent 'reader': read the file b.txt in the current directory and report its content. Sub-agent 'writer': run the shell command 'touch sub-writer.txt' in the current directory and report the result. Do not do these tasks yourself. When both finish, reply with one short line summarizing both results.
Observed
-
The turn ends with
end_turnafter 25–30 s. Both subagents did their work:sub-writer.txtexists, and the final answer reports the content ofb.txt. -
No
subagent_spawnedand nosubagent_state_updatearrive. Nosession/updatearrives for any session other than the root. -
The client only sees the
item/startedcopy of each spawn, with emptyreceiverThreadIds. No completion update follows, so the tool call staysin_progress:{"sessionUpdate":"tool_call","toolCallId":"exec-…","kind":"other","title":"spawnAgent","status":"in_progress", "rawInput":{"prompt":"You are sub-agent 'writer'. …","senderThreadId":"01a11650-…","receiverThreadIds":[],"agentsStates":{}}, "_meta":{"jetbrains":{"air":{"version":1,"subagent":true}}}} -
The
waittool calls that follow carry the child thread ids inreceiverThreadIdsand complete normally. Codex writes a separate rollout for each child thread in~/.codex/sessions. -
Approvals. With
mode=read-only, the writer's command needs approval. Nosession/request_permissionreaches the client, for the root or any other session. The parent keeps callingwaitandsendInput, and after more than 6 minutes the turn still had not finished. It looks like the approval waits for a child session that is never announced (interactionSessionId→waitForChildSession). -
Replay.
session/loadof the same session in a new process with the same capabilities replays nospawnAgenttool calls and announces no children. Only the twowaitcalls remain. -
Without native negotiation. Without the AIR capability, the same prompt behaves consistently: the
spawnAgenttool calls complete, and the writer's approval arrives on the root session.
Expected
Each spawned child is announced with subagent_spawned before its first output, as docs/subagent-sessions.md describes. Child output, approvals and elicitations use the child session id, and the child ends with subagent_state_update. The spawn tool call does not stay in_progress, and session/load rebuilds the same tree.
Cause
In src/subagents/CodexSubagentEventRouter.ts on main, materialize() is only called from the subAgentActivity branch (line 108). The spawnAgent branch only records a pending spawn (lines 127–153) and returns true (line 170), so the spawn's item/completed is treated as represented and suppressed. For an ad-hoc spawn_agent, no subAgentActivity item ever comes, so the child is never materialized. Its notifications stay in the pending buffer until it overflows, and the client is left with the item/started copy.
I did not capture APP_SERVER_LOGS this time. The ACP-side behavior matches the app-server trace in #482 (subAgentActivity items: 0, hundreds of child-thread notifications, a "Pending subagent … exceeded the notification buffer" log line).
#482's fix still looks right: announce from the spawn's own item/completed, which already carries receiverThreadIds and arrives before any child output. Named subagents keep their path-based name, because materializing a known child is a no-op.
Related
- fix: announce ad-hoc spawned subagents so their output is not dropped #482 — the same analysis and a fix for 1.7.0, closed without merge.
- Sub Agents Implementation #306 — the general "subagents are invisible to ACP clients" request, from before native subagent sessions.
Activity