`collab_tool_call` "spawn_agent" item missing from `exec --experimental-json` stream; "wait" item's `receiver_thread_ids` stays empty despite a real, successful delegation underneath
What version of Codex CLI is running?
0.144.5 (installed via @openai/codex-sdk / @openai/codex npm packages)
Which model were you using?
gpt-5.6-sol
What platform is your computer?
Linux (Ubuntu 20.04 focal), invoked via @openai/codex-sdk's Codex.startThread().runStreamed(), which shells out to codex exec --experimental-json (see node_modules/@openai/codex-sdk/src/exec.ts).
What happened?
Using native subagent support ([features] multi_agent = true, a custom
agent defined at <CODEX_HOME>/agents/<name>.toml), I asked the root agent
to spawn a named custom agent and relay its exact reply. The--experimental-json event stream only ever shows:
{"type":"item.started","item":{"id":"item_1","type":"collab_tool_call","tool":"wait","sender_thread_id":"...","receiver_thread_ids":[],"prompt":null,"agents_states":{},"status":"in_progress"}}
{"type":"item.completed","item":{"id":"item_1","type":"collab_tool_call","tool":"wait","sender_thread_id":"...","receiver_thread_ids":[],"prompt":null,"agents_states":{},"status":"completed"}}
{"type":"item.completed","item":{"id":"item_2","type":"agent_message","text":"SPECIALIST SAID: PONG"}}
Note: no collab_tool_call with tool: "spawn_agent" (or similar) ever
appears in this stream, and the wait item's receiver_thread_ids is
always [], even though the final agent_message content is correct.
This reproduced identically across 6 separate runs, with and without[features.multi_agent_v2].
Reading the FULL session rollout (~/.codex/sessions/**/*.jsonl) for the
same turn tells a different, complete story — it contains the actualfunction_call/function_call_output pair for the spawn, asub_agent_activity event ("kind": "started"), the child thread's own
rollout file (session_meta subagent.thread_spawn with the correctagent_path/agent_nickname), and the real agent_message reply routed
back from the child. state_5.sqlite's thread_spawn_edges table also
correctly records the parent→child edge for every one of the 6 runs.
So: delegation genuinely works end-to-end. The bug is that the--experimental-json stream — the one @openai/codex-sdk consumes and
exposes to SDK users — is missing the spawn_agent collab_tool_call event
entirely, and the wait item it does emit never reflects the real
receiver thread. This is different from #28318 (missing TypeScript type forcollab_tool_call — that reporter's runtime JSON did include a populatedspawn_agent item with real receiver_thread_ids); here the data itself
is missing from this event source, not just its type.
Expected behavior
exec --experimental-json (and therefore @openai/codex-sdk's public
event stream) should include the spawn_agent collab_tool_call item, andwait's receiver_thread_ids should reflect the actual spawned child
thread(s) — matching what the full session rollout already records
correctly.
Why this matters
Any SDK consumer trying to observe or verify subagent delegation
(logging, tracing, debugging, deciding whether to trust a delegated
result) via the public event stream currently gets a false negative —
delegation looks like a silent no-op even when it succeeded. We only
caught this by cross-referencing the full rollout JSONL andstate_5.sqlite directly, which isn't part of the SDK's public surface.
1 Comment
Confirmed on
codex-cli 0.145.0-alpha.18withcodex exec --jsonusing one v2 spawn, one wait, and an exactPONG-34919relay. The public JSONL still emitted only awaititem withreceiver_thread_ids: []; the matching rollout contained thespawn_agentfunction call, asub_agent_activitystartedevent with the child thread ID, and the child reply. The turn context confirmedmulti_agent_version: "v2".On current
main(99744cf), this appears to be the v2 event-shape boundary rather than the JSON serializer itself:multi_agents_v2/spawn.rsemits onlyTurnItem::SubAgentActivityafter a successful spawn.event_processor_with_jsonl_output.rsmapsCollabAgentToolCallbut has noSubAgentActivitymapping, so that observable child ID is dropped from exec JSONL.multi_agents_v2/wait.rsexplicitly emits both wait lifecycle items with empty receiver IDs.multi_agent_version_for_modelcan still select the model-provided v2 version.A focused regression would ideally exercise the v2 spawn/wait flow through the exec/app-server boundary and assert that the public JSONL exposes the spawned child ID, rather than only testing the synthetic
CollabAgentToolCallmapper. Happy to prepare that patch if the maintainers would like an external PR.