MultiAgentV2: send_message at a sampling boundary can leave a live subagent running but unscheduled

Open 💬 2 comments Opened Jul 13, 2026 by Lady-Lin

Environment

  • Surface: Codex Desktop / app-server, MultiAgentV2
  • Rollout-recorded CLI version: 0.144.1
  • Current local CLI version: 0.144.3
  • Subscription: Pro
  • Model: gpt-5.6-sol, maximum reasoning effort
  • Platform: Linux 7.0.0-27-generic x86_64 GNU/Linux

What issue are you seeing?

A queue-only send_message delivered near a model sampling boundary can leave a live subagent reported as running, but with no subsequent turn scheduled.

This was observed twice in one redacted Codex Desktop MultiAgentV2 workflow:

| Attempt | Completed child tool outputs before the status message | Error events | Delivery metadata | Final outcome |
| --- | ---: | ---: | --- | --- |
| Full implementation task | 12 | 0 | trigger_turn=false | No later child tool/final event; parent eventually interrupted it |
| Narrowed implementation task | 8 | 0 | trigger_turn=false | Same behavior; parent eventually interrupted it |

Before each status message, the child was actively reading files and designing tests. The parent then used send_message to ask for the current phase without intending to interrupt the work. In each child rollout, the message was appended immediately after a reasoning response with:

{"type":"inter_agent_communication_metadata","payload":{"trigger_turn":false}}

After that event, the child produced no more tools, messages, or final response. Repeated wait_agent calls timed out, while list_agents continued to report the child as running.

No tool error, stream error, rate-limit event, shell hang, or application exception occurred. Raw rollouts are not attached because they contain private workspace paths and task content.

What steps can reproduce the bug?

The timing appears to matter.

  1. Enable MultiAgentV2 in Codex Desktop/app-server.
  2. Spawn a no-history child with a multi-step task that performs several tool calls before writing files:

``json
{
"task_name": "implementation_probe",
"fork_turns": "none",
"message": "Inspect the relevant interfaces, write a failing test, implement the change, verify it, and report."
}
``

  1. Wait until the child has completed several read-only tool calls and is sampling after a tool output.
  2. Ask for a status update using queue-only messaging:

``json
{
"target": "/root/implementation_probe",
"message": "Report your current phase and any blocker."
}
``

  1. Inspect the child rollout. The incoming communication is recorded with trigger_turn=false at the sampling boundary.
  2. Call wait_agent and list_agents repeatedly.
  3. Observe that no new child turn is scheduled, wait_agent times out, and the child remains reported as running.
  4. Repeat with a smaller task. The same event sequence was observed.

What is the expected behavior?

A queue-only message should not strand an already-live task.

At least one of these should happen:

  • the child continues its original task loop and consumes the queued message at a later safe boundary;
  • the runtime schedules the next natural child turn even though the message itself is queue-only;
  • or the agent is reported as idle/pending-mail rather than running, with an actionable hint to use followup_task.

It would also help if list_agents exposed fields such as turn_active, pending_mailbox_items, and last_activity_at, so a controller can distinguish active sampling from a logically open but unscheduled task. A wait_agent timeout should remain a mailbox timeout, but the returned status could make this distinction explicit.

Additional information

The queue-only/trigger-turn distinction is intentional in current source: send_message uses QueueOnly, while followup_task uses TriggerTurn:

https://github.com/openai/codex/blob/92938d880eccbad1242a86a63f819f67780f68c0/codex-rs/core/src/tools/handlers/multi_agents_v2/message_tool.rs#L9-L30

Related but distinct: #32353 reports a completed agent with pending queue-only mail pinning a residency slot and causing AgentLimitReached. This report does not involve capacity exhaustion or an already-completed child. It concerns a live task that remains shown as running after queue-only delivery but receives no later turn.

The report is intentionally sanitized: no repository name, workspace path, session/thread ID, original task text, local username, or private rollout payload is included.

View original on GitHub ↗

This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗