MultiAgentV2 spawn_agent can hang indefinitely while evicting a terminal resident thread

Open 💬 2 comments Opened Jul 17, 2026 by surlymochan

What version of the Codex App are you using?

Originally observed with:

  • Codex App 26.707.72221 (build 5307)
  • Bundled CLI 0.144.2

Current installed version:

  • Codex App 26.715.21425 (build 5488)
  • Bundled CLI 0.145.0-alpha.18

The relevant code path is still unchanged in 0.144.5, 0.145.0-alpha.20, and current main.

What subscription do you have?

ChatGPT Pro

What platform is your computer?

Darwin 25.5.0 arm64 arm
macOS 26.5.1 (25F80)

What issue are you seeing?

In a long-lived Codex Desktop session using MultiAgentV2, spawn_agent can hang indefinitely before the child thread is created.

The parent rollout ends with a structured collaboration.spawn_agent function call, but there is no corresponding function-call output, no child session_meta, and no child activity. The parent turn remains active/inProgress indefinitely.

This is different from a slow child and from the normal concurrency-limit path:

  • Successful spawns: median 3.3s, P95 19.8s, maximum observed 34.2s.
  • Explicit agent thread limit reached failures: 0.05–0.83s and always returned a structured error.
  • Three affected calls had no output and created no child session.
  • A fourth near-reproduction was manually aborted after 264.5s and also created no child session.

What steps can reproduce the bug?

The failure is intermittent, but the repeated incidents share this lifecycle:

  1. Use a long-lived Codex Desktop thread with MultiAgentV2.
  2. Spawn enough direct children to reach the resident-agent capacity.
  3. Let one or more children become terminal (Completed, Errored, or Interrupted) while remaining resident.
  4. Call spawn_agent again, causing reserve_slot to evict an LRU terminal resident.
  5. In the affected state, spawn_agent never returns and no child thread is created.

Two independent hangs were observed in the same parent-thread family, followed by another zero-output spawn in a direct child.

What is the expected behavior?

spawn_agent should always complete within a bounded time:

  • create and return the child thread ID;
  • return AgentLimitReached; or
  • return a structured cleanup/shutdown timeout error.

A stuck terminal resident must not wedge the parent turn indefinitely.

Additional information

The likely blocking path is MultiAgentV2 resident eviction:

  • try_unload_one_resident removes the candidate from the LRU resident deque with pop_lru_candidate;
  • it then awaits ensure_rollout_materialized();
  • it then awaits shutdown_and_wait();
  • neither await has a timeout;
  • there is no cancellation guard that restores the popped resident ID if the parent tool future is cancelled.

Source, current alpha:

https://github.com/openai/codex/blob/rust-v0.145.0-alpha.20/codex-rs/core/src/agent/control/residency.rs#L116-L149

The residency.rs implementation is byte-identical between 0.145.0-alpha.18, 0.145.0-alpha.20, and current main. The stable 0.144.5 path contains the same unbounded awaits.

This also creates a possible accounting leak: the candidate is popped before the unbounded awaits, so cancellation can leave the thread loaded but no longer represented in the resident deque.

Related reports cover similar lifecycle symptoms, but not this V2 pre-create eviction path:

  • #24389 — V1 close_agent hangs on an unresponsive child
  • #25426 — V1 termination wait is unbounded
  • #18335 — agent spawn slots leak across persistent turns

Suggested fix:

  1. Add a bounded timeout around rollout materialization and shutdown during eviction.
  2. Add a cancellation-safe guard that restores or quarantines the popped resident.
  3. Return a structured spawn cleanup timed out result rather than hanging.
  4. Add stage telemetry keyed by spawn call ID: reserve → select LRU → materialize → shutdown → remove → fork → child created.
  5. Add regression tests using a terminal resident whose session loop never terminates.

I have omitted local paths, session IDs, prompts, and raw logs from this public report. The redacted call lifecycle and timing evidence can be provided privately if needed.

View original on GitHub ↗

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