Codex Desktop: completed subagents can remain as hidden resident blockers and prevent later spawns

Open 💬 3 comments Opened Jul 21, 2026 by Yurdos-r

Summary

In Codex Desktop, after the root agent spawns three direct subagents and their tasks appear to be completed, a later spawn_agent can fail with:

collab spawn failed: agent thread limit reached

The user-facing problem is that a subagent shown as completed can still be a non-evictable resident because of hidden pending mailbox state. The UI and the generic error do not identify the blocking agent or provide a recovery action.

Environment

  • Product: Codex Desktop
  • Platform: Windows
  • Date reproduced: 2026-07-21
  • App/CLI version: not exposed to the isolated test thread, so I cannot report it reliably
  • Observed collaboration capacity: root plus three direct subagent resident slots
  • No files or configuration were changed during the reproduction

Original user-facing symptom

  1. Ask the root agent to delegate work to three subagents.
  2. Let the delegated tasks finish.
  3. Continue the same Codex task and ask it to delegate another task.
  4. The later spawn fails or delegation is unavailable even though the earlier subagents appear completed.

From the UI, this looks like completed subagents failed to release their capacity.

Controlled reproduction

I ran two isolated Codex tasks to separate ordinary completion from hidden mailbox state.

Baseline: ordinary completed agents

  1. Spawn direct subagents A, B, and C with clean history.
  2. Let all three return A_OK, B_OK, and C_OK and reach completed.
  3. Spawn a new direct subagent D.

Result: D's spawn_agent returned successfully without a noticeable wait, and D completed with D_OK.

This shows that ordinary completed agents alone were reclaimable in this runtime.

Pending-mailbox variant

  1. In a fresh isolated task, spawn A and let it complete with A_OK.
  2. Send STATUS_PROBE to completed A using queue-only send_message. It does not trigger a new turn.
  3. Spawn E and F with tasks that remain active for about 45 seconds.
  4. While E and F are active, attempt to spawn G.

The G spawn fails immediately with the exact output:

collab spawn failed: agent thread limit reached

At that point only E and F are actively running. A is completed, but it has the pending queue-only mailbox item. The controlled difference from the successful baseline is the hidden pending mailbox on A.

I then used followup_task on A; A consumed the queued work and returned A_DRAINED. E and F also completed. A subsequent retry could not be emitted because the isolated test task encountered a separate tool-routing failure, so this report does not claim that the recovery path was independently verified.

Actual behavior

  • A completed subagent can remain resident and block admission.
  • The completed status does not expose pending mailbox state.
  • AgentLimitReached does not distinguish active concurrency exhaustion from residency pressure.
  • The user cannot see which completed agent is blocking eviction or how to recover.
  • Raising the thread limit would only make the problem less frequent.

Expected behavior

At least one of the following should occur:

  1. Reject queue-only send_message to an already completed agent and direct the caller to followup_task.
  2. Automatically trigger a follow-up turn when queue-only mail crosses the completion boundary.
  3. Persist the mailbox independently so the completed resident can be evicted.
  4. Expose a close/drain action for completed agents.
  5. Make AgentLimitReached report max, active, residents, and any pending_mailbox_blockers.
  6. Show in the Desktop UI when a completed subagent still counts toward capacity.

Related issues

  • #32353 documents the pending-mailbox residency mechanism at the app-server level.
  • #22779 covers older completed/open-thread quota accounting.
  • #33777 covers a separate MultiAgentV2 hang while evicting a terminal resident.

This report is filed separately to track the Codex Desktop user-facing symptom: agents appear completed, but hidden residency state prevents later delegation with no diagnostic or recovery hint.

Additional evidence

Independent reproduction comment on #32353:

https://github.com/openai/codex/issues/32353#issuecomment-5033237101

No local paths, task/thread IDs, account information, or private logs are included.

View original on GitHub ↗

3 Comments

slobodaapl · 1 month ago

Can confirm it also happens in CLI; root agent tries to spawn agents; after they're done, they're no longer visible to the root agent, and thus if it tries to spawn another past the limit, it errors out on agent cap being reached, but it cannot close old ones as it cannot see them.

pomazanbohdan · 9 days ago

Additional Windows Desktop reproduction (26.814.5167.0; CLI/runtime 0.148.0-alpha.15):

  • A backup-first cleanup left the session with 0 persisted subagent rows and 0 spawn edges.
  • The same root then spawned 7 direct subagents. Each child rollout reached task_complete/final_answer, but the parent persisted all 7 spawn edges as open; the parent recorded started/interacted activity without a terminal/completed edge transition.
  • After several context_compacted events, one already-completed child reappeared as pending_init. With that hidden resident plus two genuinely running reviewers, a third reviewer failed with collab spawn failed: agent thread limit reached.
  • The router log records this at codex_core::tools::router, core/src/tools/router.rs:231. All 7 child rollouts later completed while all 7 parent edges remained open.

This indicates a completion-to-residency/edge-release gap, with compaction/rehydration reviving a stale resident handle. A regression test should compare child terminal state with parent edge closure and ensure completed/pending_init residents do not consume spawn capacity.

pomazanbohdan · 5 days ago

Follow-up cluster note: the same boundary affects both capacity and presentation—terminal children can retain open parent edges/residency, then rehydrate as Active after reload. The safe invariant is rollout terminal state plus live-process absence, not UI Active or thread_spawn_edges.status=open; see #39694 and #37042.