MultiAgentV2: followup_task to a non-child descendant reports completion to its spawn parent, leaving the requester's wait_agent blocked
What version of Codex CLI is running?
codex-cli 0.144.1
What subscription do you have?
ChatGPT (chatgpt_plan_type: prolite)
Which model were you using?
gpt-5.6-sol (root agent, reasoning effort: high)
What platform is your computer?
Linux 6.18.33.2-microsoft-standard-WSL2 x86_64 x86_64
What terminal emulator and version are you using (if applicable)?
Not applicable — headless invocation via codex exec --json from an external driver process.
Codex doctor report
{
"schemaVersion": 1,
"generatedAt": "1783698963s since unix epoch", "overallStatus": "ok",
"codexVersion": "0.144.1",
"checks": {
"app_server.status": {
"id": "app_server.status",
"category": "app-server",
"status": "ok",
"summary": "background server is not running", "details": { "control socket": "/home/kino/.codex/app-server-control/app-server-control.sock",
"daemon state dir": "/home/kino/.codex/app-server-daemon",
"mode": "ephemeral",
"pid file": "/home/kino/.codex/app-server-daemon/app-server.pid (missing)",
"settings": "/home/kino/.codex/app-server-daemon/settings.json (missing)",
"status": "not running",
"update-loop pid file": "/home/kino/.codex/app-server-daemon/app-server-updater.pid (missing)"
},
"remediation": null,
"durationMs": 0
},
"auth.credentials": {
"id": "auth.credentials",
"category": "auth",
"status": "ok",
"summary": "auth is configured",
"details": {
"auth file": "/home/kino/.codex/auth.json",
"auth storage mode": "File",
"stored API key": "false",
"stored ChatGPT tokens": "true",
"stored agent identity": "false",
"stored auth mode": "chatgpt"
},
"remediation": null,
"durationMs": 0
},
"config.load": {
"id": "config.load",
"category": "config",
"status": "ok",
"summary": "config loaded",
"details": {
"CODEX_HOME": "/home/kino/.codex",
"config.toml": "/home/kino/.codex/config.toml",
"config.toml parse": "ok",
"cwd": "/home/kino/src/codex",
"enabled feature flags": "shell_tool, unified_exec, shell_snapshot, code_mode_host, terminal_resize_reflow, sqlite, hooks, use_linux_sandbox_bwrap, enable_request_compression, multi_agent, apps, tool_search_always_defer_mcp_tools, tool_suggest, plugins, in_app_browser, browser_use, browser_use_full_cdp_access, browser_use_external, computer_use, remote_plugin, plugin_sharing, image_generation, resize_all_images, skill_mcp_dependency_install, mentions_v2, steer, guardian_approval, goals, collaboration_modes, tool_call_mcp_elicitation, auth_elicitation, personality, fast_mode, tui_app_server, prevent_idle_sleep, remote_compaction_v2, workspace_dependencies",
"feature flag overrides": "use_linux_sandbox_bwrap=true, prevent_idle_sleep=true",
"feature flags enabled": "37",
"log dir": "/home/kino/.codex/log",
"mcp servers": "0",
"model": "gpt-5.3-codex-spark",
"model provider": "openai",
"sqlite home": "/home/kino/.codex"
},
"remediation": null,
"durationMs": 0
},
"git.environment": {
"id": "git.environment",
"category": "git",
"status": "ok",
"summary": "git version 2.34.1",
"details": {
".git entry": "directory",
"PATH git #1": "/usr/bin/git",
"PATH git #2": "/bin/git",
"PATH git entries": "2",
"git branch": "main",
"git build options": "git version 2.34.1; cpu: x86_64; no commit associated with this build; sizeof-long: 8; sizeof-size_t: 8; shell-path: /bin/sh",
"git exec path": "/usr/lib/git-core",
"git version": "git version 2.34.1",
"repo detected": "true",
"repo root": "/home/kino/src/codex",
"selected git": "/usr/bin/git"
},
"remediation": null,
"durationMs": 119
},
"installation": {
"id": "installation",
"category": "install",
"status": "ok",
"summary": "installation looks consistent",
"details": {
"PATH codex #1": "/home/kino/.nvm/versions/node/v24.14.0/bin/codex",
"current executable": "/home/kino/.nvm/versions/node/v24.14.0/lib/node_modules/@openai/codex/node_modules/@openai/codex-linux-x64/vendor/x86_64-unknown-linux-musl/bin/codex",
"install context": "npm (package /home/kino/.nvm/versions/node/v24.14.0/lib/node_modules/@openai/codex/node_modules/@openai/codex-linux-x64/vendor/x86_64-unknown-linux-musl, bin /home/kino/.nvm/versions/node/v24.14.0/lib/node_modules/@openai/codex/node_modules/@openai/codex-linux-x64/vendor/x86_64-unknown-linux-musl/bin, resources /home/kino/.nvm/versions/node/v24.14.0/lib/node_modules/@openai/codex/node_modules/@openai/codex-linux-x64/vendor/x86_64-unknown-linux-musl/codex-resources, path /home/kino/.nvm/versions/node/v24.14.0/lib/node_modules/@openai/codex/node_modules/@openai/codex-linux-x64/vendor/x86_64-unknown-linux-musl/codex-path)",
"managed by bun": "false",
"managed by npm": "true",
"managed by pnpm": "false",
"managed package root": "/home/kino/.nvm/versions/node/v24.14.0/lib/node_modules/@openai/codex",
"npm update target": "/home/kino/.nvm/versions/node/v24.14.0/lib/node_modules/@openai/codex"
},
"remediation": null,
"durationMs": 131
},
"mcp.config": {
"id": "mcp.config",
"category": "mcp",
"status": "ok",
"summary": "no MCP servers configured",
"details": {},
"remediation": null,
"durationMs": 0
},
"network.env": {
"id": "network.env",
"category": "network",
"status": "ok",
"summary": "network-related environment looks readable",
"details": {
"proxy env vars": "none"
},
"remediation": null,
"durationMs": 0
},
"network.provider_reachability": {
"id": "network.provider_reachability",
"category": "reachability",
"status": "ok",
"summary": "active provider endpoints are reachable over HTTP",
"details": {
"ChatGPT base URL": "https://chatgpt.com/backend-api/ reachable (HTTP 404)",
"reachability mode": "ChatGPT auth"
},
"remediation": null,
"durationMs": 1092
},
"network.websocket_reachability": {
"id": "network.websocket_reachability",
"category": "websocket",
"status": "ok",
"summary": "Responses WebSocket handshake succeeded",
"details": {
"DNS": "2 IPv4, 2 IPv6, first IPv6",
"auth mode": "chatgpt",
"connect timeout": "15000 ms",
"endpoint": "wss://chatgpt.com/backend-api/<redacted>",
"handshake result": "HTTP 101 Switching Protocols",
"model provider": "openai",
"models etag present": "true",
"provider name": "OpenAI",
"proxy env vars": "none",
"reasoning header": "false",
"server model present": "false",
"supports websockets": "true",
"wire API": "responses"
},
"remediation": null,
"durationMs": 645
},
"runtime.provenance": {
"id": "runtime.provenance",
"category": "runtime",
"status": "ok",
"summary": "running npm on linux-x86_64",
"details": {
"commit": "unknown",
"current executable": "/home/kino/.nvm/versions/node/v24.14.0/lib/node_modules/@openai/codex/node_modules/@openai/codex-linux-x64/vendor/x86_64-unknown-linux-musl/bin/codex",
"install method": "npm (package /home/kino/.nvm/versions/node/v24.14.0/lib/node_modules/@openai/codex/node_modules/@openai/codex-linux-x64/vendor/x86_64-unknown-linux-musl, bin /home/kino/.nvm/versions/node/v24.14.0/lib/node_modules/@openai/codex/node_modules/@openai/codex-linux-x64/vendor/x86_64-unknown-linux-musl/bin, resources /home/kino/.nvm/versions/node/v24.14.0/lib/node_modules/@openai/codex/node_modules/@openai/codex-linux-x64/vendor/x86_64-unknown-linux-musl/codex-resources, path /home/kino/.nvm/versions/node/v24.14.0/lib/node_modules/@openai/codex/node_modules/@openai/codex-linux-x64/vendor/x86_64-unknown-linux-musl/codex-path)",
"platform": "linux-x86_64",
"version": "0.144.1"
},
"remediation": null,
"durationMs": 0
},
"runtime.search": {
"id": "runtime.search",
"category": "search",
"status": "ok",
"summary": "search is OK (bundled)",
"details": {
"search command": "/home/kino/.nvm/versions/node/v24.14.0/lib/node_modules/@openai/codex/node_modules/@openai/codex-linux-x64/vendor/x86_64-unknown-linux-musl/codex-path/rg",
"search command readiness": "file exists",
"search provider": "bundled"
},
"remediation": null,
"durationMs": 0
},
"sandbox.helpers": {
"id": "sandbox.helpers",
"category": "sandbox",
"status": "ok",
"summary": "sandbox configuration is readable",
"details": {
"approval policy": "OnRequest",
"codex-linux-sandbox helper": "/home/kino/.codex/tmp/arg0/codex-arg0HKGkx6/codex-linux-sandbox",
"execve wrapper helper": "/home/kino/.codex/tmp/arg0/codex-arg0HKGkx6/codex-execve-wrapper",
"filesystem sandbox": "restricted",
"network sandbox": "restricted"
},
"remediation": null,
"durationMs": 0
},
"state.paths": {
"id": "state.paths",
"category": "state",
"status": "ok",
"summary": "state paths and databases are inspectable",
"details": {
"CODEX_HOME": "/home/kino/.codex (dir)",
"active rollout files": "1295 files, 2078007127 total bytes, 1604638 average bytes",
"archived rollout files": "0 files, 0 total bytes, 0 average bytes",
"goals DB": "/home/kino/.codex/goals_1.sqlite (file)",
"goals DB integrity": "ok",
"log DB": "/home/kino/.codex/logs_2.sqlite (file)",
"log DB integrity": "ok",
"log dir": "/home/kino/.codex/log (dir)",
"memories DB": "/home/kino/.codex/memories_1.sqlite (file)",
"memories DB integrity": "ok",
"sqlite home": "/home/kino/.codex (dir)",
"state DB": "/home/kino/.codex/state_5.sqlite (file)",
"state DB integrity": "ok"
},
"remediation": null,
"durationMs": 1522
},
"state.rollout_db_parity": {
"id": "state.rollout_db_parity",
"category": "threads",
"status": "ok",
"summary": "rollout files and state DB thread inventory agree",
"details": {
"default model provider": "openai",
"rollout DB active files": "1295",
"rollout DB active rows": "1295",
"rollout DB archive mismatches": "0",
"rollout DB archived files": "0",
"rollout DB archived rows": "0",
"rollout DB duplicate DB paths": "0",
"rollout DB duplicate rollout thread ids": "0",
"rollout DB malformed file names": "0",
"rollout DB missing active rows": "0",
"rollout DB missing archived rows": "0",
"rollout DB model providers": "openai=1295",
"rollout DB rows": "1295",
"rollout DB scan cap reached": "false",
"rollout DB scan errors": "0",
"rollout DB sources": "subagent:thread_spawn=551, cli=397, exec=252, vscode=73, mcp=22",
"rollout DB stale rows": "0"
},
"remediation": null,
"durationMs": 34279
},
"system.environment": {
"id": "system.environment",
"category": "system",
"status": "ok",
"summary": "OS language C",
"details": {
"EDITOR": "not set",
"LANG": "C.UTF-8",
"VISUAL": "not set",
"os": "Ubuntu 22.4.0 (jammy) [64-bit]",
"os language": "C",
"os type": "Ubuntu",
"os version": "22.4.0"
},
"remediation": null,
"durationMs": 30
},
"terminal.env": {
"id": "terminal.env",
"category": "terminal",
"status": "ok",
"summary": "terminal metadata was detected",
"details": {
"COLORTERM": "truecolor",
"DISPLAY": "present",
"WAYLAND_DISPLAY": "present",
"WSL_DISTRO_NAME": "present",
"WSL_INTEROP": "present",
"WT_SESSION": "present",
"color output": "enabled",
"effective locale": "C.UTF-8",
"stderr is terminal": "true",
"stdin is terminal": "true",
"stdout is terminal": "true",
"terminal": "Windows Terminal",
"terminal size": "119x74"
},
"remediation": null,
"durationMs": 0
},
"terminal.title": {
"id": "terminal.title",
"category": "title",
"status": "ok",
"summary": "terminal title default",
"details": {
"terminal title activity": "true",
"terminal title items": "activity, project-name",
"terminal title project source": "git repo root",
"terminal title project value": "codex",
"terminal title source": "default"
},
"remediation": null,
"durationMs": 0
},
"updates.status": {
"id": "updates.status",
"category": "updates",
"status": "ok",
"summary": "update configuration is locally consistent",
"details": {
"cached latest version": "0.144.1",
"check for update on startup": "true",
"last checked at": "2026-07-10T11:37:54.349307646Z",
"latest version": "0.144.1",
"latest version status": "current version is not older",
"npm update target": "/home/kino/.nvm/versions/node/v24.14.0/lib/node_modules/@openai/codex",
"update action": "npm install -g @openai/codex",
"version cache": "/home/kino/.codex/version.json"
},
"remediation": null,
"durationMs": 144
}
}
}
What issue are you seeing?
When a root agent uses MultiAgentV2 followup_task to reassign work to an
existing depth-2 descendant (a grandchild, addressed by its canonical path,
e.g. /root/research_ops/ic_source_gate), the target accepts and completes the
task, but the terminal result is delivered to the target's spawn-time direct
parent (/root/research_ops) instead of the requester (/root). Sincewait_agent only observes the caller's own mailbox, the root agent never wakes
for that completion and stays blocked until its wait timeout (1 hour in our
case).
The reused grandchild finished successfully while root was already waiting:
its rollout persisted event_msg.task_complete with a full, valid report at14:04:11Z, about two minutes after root's wait_agent began. Root never woke
for it and blocked for the full hour, until the wait returned{"message":"Wait timed out.","timed_out":true}.
The bug then reproduced a second time in the same run. After the timeout, root
re-issued followup_task to its two direct children and to the same
grandchild, then waited again. Both direct-child completions woke root'swait_agent normally; the grandchild's second completion (15:06:11Z) landed
while a wait_agent was actively pending and did not wake it — that wait
returned only at 15:06:47Z, for a direct child. In total, direct-child
follow-up completions woke root 4 out of 4 times; the cross-ancestor
grandchild's completions never woke it (0 out of 2, both times with a wait
provably pending). This rules out a generic completion-notification loss and a
wait-not-yet-started race.
Final root-session event (from codex exec --json output) — an in-progress
wait that never completed; the session stayed frozen here:
{
"type": "item.started",
"item": {
"id": "item_24",
"type": "collab_tool_call",
"tool": "wait",
"sender_thread_id": "019f4c35-c872-7913-b3c8-42a6ccf13fb1",
"receiver_thread_ids": [],
"prompt": null,
"agents_states": {},
"status": "in_progress"
}
}
Evidence that the result was routed to the intermediate parent
Incident thread IDs (2026-07-10, UTC):
- root:
019f4c35-c872-7913-b3c8-42a6ccf13fb1 - intermediate parent
/root/research_ops:019f4c37-6ff0-79f2-9a6f-382a1244a425 - reused grandchild
/root/research_ops/ic_source_gate:019f4c46-6847-7593-a380-2c9854abc0fa
The mailbox is in-memory, so the intermediate parent's mailbox contents are not
persisted directly; the routing destination is established by the following
persisted facts:
- The grandchild's rollout persisted the terminal
TurnCompleteevent
(serialized as event_msg.task_complete) for both follow-ups, at
14:04:11.590Z and 15:06:11.296Z — exactly the event
maybe_notify_parent_of_terminal_turn acts on.
- The completion recipient is fixed by the grandchild's persisted spawn
metadata (session_meta.source.thread_spawn.parent_thread_id = =
019f4c37-6ff0-79f2-9a6f-382a1244a425/root/research_ops), which is the
only input the routing code uses.
- The identical delivery path demonstrably worked four times in the same
process: the direct children's completions woke root's wait_agent at
14:02:05Z, 14:02:20Z, 15:05:10Z, and 15:06:45Z.
- A root
wait_agentwas pending at both grandchild completions and returned
for neither: the first wait ran to its 1-hour timeout, and the wait pending
at 15:06:11Z returned only at 15:06:47Z, woken by a direct child. The
results never reached root's mailbox.
- The intermediate parent had completed its own turn at
13:52:55Z; result
messages are sent with trigger_turn: false, so it was never started to
relay the messages, and its rollout still ends there.
Grandchild rollout session_meta proving spawn-time ownership:
{
"session_id": "019f4c35-c872-7913-b3c8-42a6ccf13fb1",
"id": "019f4c46-6847-7593-a380-2c9854abc0fa",
"parent_thread_id": "019f4c37-6ff0-79f2-9a6f-382a1244a425",
"source": {
"subagent": {
"thread_spawn": {
"parent_thread_id": "019f4c37-6ff0-79f2-9a6f-382a1244a425",
"depth": 2,
"agent_path": "/root/research_ops/ic_source_gate",
"agent_nickname": "Planck",
"agent_role": null
}
}
}
}
Timeline excerpt (UTC):
13:54:04 spawn /root/alt_physical fails: agent thread limit reached
13:54:24 followup_task on /root/research_ops/ic_source_gate succeeds
13:54:27 root calls wait_agent (timeout_ms 3600000)
14:02:05 /root/alt_digital completes -> wait returns (direct child, OK)
14:02:09 root calls wait_agent again
14:02:20 /root/fundamental completes -> wait returns (direct child, OK)
14:02:24 root calls wait_agent a third time
14:04:11 grandchild persists task_complete with full report
-- root never wakes for it; blocks for the full 1-hour timeout
15:03:28 wait returns {"message":"Wait timed out.","timed_out":true}
15:03:42 root retries: followup_task on /root/fundamental
15:03:48 followup_task on /root/alt_digital
15:03:52 followup_task on /root/research_ops/ic_source_gate (same grandchild)
15:03:57 root calls wait_agent
15:05:10 /root/fundamental completes -> wait returns (direct child, OK)
15:05:13 root calls wait_agent
15:06:11 grandchild persists its second task_complete -> pending wait does NOT return
15:06:45 /root/alt_digital completes -> that same wait returns (direct child, OK)
15:06:47 root calls wait_agent; nothing follows; session frozen here
Root cause (traced in source, rust-v0.144.1; unchanged at 6138909d6e)
The contract between followup_task targeting and completion routing is
inconsistent:
- Cross-ancestor targeting is accepted.
resolve_agent_targetaccepts any
known thread ID directly, and resolve_agent_reference resolves canonical
paths without enforcing that the target is a direct child of the caller (its
caller-thread-ID parameter is received but unused, named
_current_thread_id):
codex-rs/core/src/tools/handlers/multi_agents_v2/message_tool.rs:59-129codex-rs/core/src/agent/agent_resolver.rs:8-28codex-rs/core/src/agent/control.rs:326-345
The tool spec also only says "existing non-root target agent"
(codex-rs/core/src/tools/handlers/multi_agents_spec.rs:185-210).
- Completion routing ignores the requester. On every terminal child turn,
maybe_notify_parent_of_terminal_turn derives the recipient from the
thread's spawn-time SessionSource::SubAgent(SubAgentSource::ThreadSpawn { and from
parent_thread_id, .. })agent_path.rsplit_once('/') — both yield
the original direct parent. No per-turn completion recipient or assignment
ID is recorded at followup_task submission time:
codex-rs/core/src/session/mod.rs:1767-1793,1812-1907
- The requester's wait cannot see it.
wait_agentsubscribes only to the
calling session's own InputQueue
(codex-rs/core/src/tools/handlers/multi_agents_v2/wait.rs:68-97,
codex-rs/core/src/session/input_queue.rs:34-81). The result lands in the
intermediate parent's mailbox with trigger_turn: false
(codex-rs/core/src/session/handlers.rs:288-303), so the already-completed
intermediate parent is not started to relay it either. The result is
effectively orphaned.
This is core session-routing code shared by TUI and exec, so it is notcodex exec-specific.
What steps can reproduce the bug?
Minimal reproduction (thread limit only matters as the incentive for the model
to reuse an agent; the routing bug needs just a cross-ancestor followup_task):
- Run a root agent with MultiAgentV2 collaboration tools enabled
(codex exec --json or TUI — same core path).
- Root spawns a child agent; the child spawns a grandchild; the grandchild
completes its task.
- From root, call
followup_tasktargeting the completed grandchild by its
canonical path (e.g. /root/child/grandchild). The call is accepted.
- The grandchild executes the new task and reaches a terminal turn.
- Root calls
wait_agent.
Result: the grandchild's completion is routed to the intermediate child's
mailbox (its spawn-time parent), not to root. Root's wait_agent never wakes
and blocks until timeout.
In our incident the model chose step 3 deliberately after hittingcollab spawn failed: agent thread limit reached andcollab tool failed: agent thread limit reached, then finding the completed
grandchild via list_agents — i.e. this is a natural path for a model under
thread-limit pressure, not an exotic misuse. It then repeated the same choice
after the 1-hour wait timeout, reproducing the hang twice in one run.
What is the expected behavior?
The requester must not hang. When a followup_task call is accepted and the
target completes the task, the requester's wait_agent must be able to observe
that completion; if that cannot be guaranteed, the call should fail at dispatch
instead of being accepted. Silently blocking until the wait timeout — and again
on retry — is the one outcome that should be impossible.
Note that flipping trigger_turn: true on the completion message alone would
not fix this — it would wake the wrong parent.
Additional information
- The follow-up work itself succeeded both times; only the result delivery is
misrouted. This makes the failure look like a silent hang from the
orchestrator's perspective, and retrying reproduces it deterministically.
- Test coverage gap: existing tests only cover direct-parent cases —
multi_agent_v2_followup_task_completion_notifies_parent_on_every_turn
(codex-rs/core/src/tools/handlers/multi_agents_tests.rs:1960-2111) and
plaintext_multi_agent_v2_completion_sends_agent_message
(codex-rs/core/tests/suite/subagent_notifications.rs:1205-1335). No test
exercises root → followup_task(grandchild) → grandchild completion →
root wait_agent.
- Searched existing issues (2026-07-10); no duplicate found. Adjacent but
distinct open issues:
- #26822 — subagents shut down without delivering results (children
misbehaved; here the child succeeded)
- #15723 — background subagents don't wake an idle parent
(trigger_turn: false auto-wake; here the requester was actively waiting
on the wrong mailbox)
- #26728 — completion lost after parent compaction (no compaction occurred
here)
- #23296 — subagents fail to execute the spawn task (here the task executed
correctly)
- Possibly relevant history: mailbox wait introduced in #16010, path-based
agent addressing in #15313, assign_task → followup_task rename in
#25636. The queued-mail fix #18968 addresses a different race.
This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗