Skip to content

MultiAgentV2: followup_task to a non-child descendant reports completion to its spawn parent, leaving the requester's wait_agent blocked #32203

Description

@kinoh

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). Since
wait_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 at
14: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's
wait_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 TurnComplete event
    (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_agent was 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:

  1. Cross-ancestor targeting is accepted. resolve_agent_target accepts 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-129
    • codex-rs/core/src/agent/agent_resolver.rs:8-28
    • codex-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).

  2. 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 { parent_thread_id, .. }) and from 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
  3. The requester's wait cannot see it. wait_agent subscribes 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 not
codex 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):

  1. Run a root agent with MultiAgentV2 collaboration tools enabled
    (codex exec --json or TUI — same core path).
  2. Root spawns a child agent; the child spawns a grandchild; the grandchild
    completes its task.
  3. From root, call followup_task targeting the completed grandchild by its
    canonical path (e.g. /root/child/grandchild). The call is accepted.
  4. The grandchild executes the new task and reaches a terminal turn.
  5. 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 hitting
collab spawn failed: agent thread limit reached and
collab 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

Metadata

Metadata

Assignees

No one assigned

    Labels

    CLIIssues related to the Codex CLIbugSomething isn't workingsubagentIssues involving subagents or multi-agent features

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions