Bound v1 close_agent shutdown waits for unresponsive child agents
Problem
multi_agent_v1.close_agent can block the parent turn indefinitely when a child agent accepts Op::Shutdown but its session loop never reaches termination.
This makes cleanup unsafe to put on the critical path: a parent that is otherwise done can become stuck for hours just trying to close an unresponsive child.
Related existing issues:
- #25870
- #24389
Fresh reproduction evidence
Observed in a local native multi-agent Codex session on 2026-06-24.
Environment:
- parent session:
019ef523-5927-74e2-b687-369f17ae6bbc - child session:
019ef7e2-c130-72d1-9b3e-280ff9c36e90 - child role/nickname: native
explorer,Hilbert - platform: macOS arm64
- parent CLI around run:
0.138.0, child CLI around run:0.141.0, current local CLI after update:0.142.0
Timeline from local rollout JSONL:
2026-06-24T04:28:15Z spawn_agent -> Hilbert 019ef7e2-c130-72d1-9b3e-280ff9c36e90
2026-06-24T04:48:42Z wait_agent timeout_ms=1000 -> {"status":{},"timed_out":true}
2026-06-24T04:50:29Z wait_agent timeout_ms=10000 -> {"status":{},"timed_out":true}
2026-06-24T04:50:46Z wait_agent timeout_ms=60000 -> {"status":{},"timed_out":true}
2026-06-24T04:51:56Z close_agent Hilbert, plus cleanup closes for two sibling agents
2026-06-24T11:56:27Z close_agent returned only after user abort
The tool outputs reported approximately:
aborted by user after 24930.6s
aborted by user after 24930.5s
So the close path occupied about 7h04m wall-clock and only ended because the user aborted it.
The child transcript contained session metadata, task start/abort lifecycle, and no useful assistant/tool result.
Current code path
Current main still appears to have this shape:
state.send_op(agent_id, Op::Shutdown {}).await
thread.wait_until_terminated().await
The wait on thread.wait_until_terminated() is unbounded. If shutdown/task abort/lifecycle cleanup never completes, close_agent blocks the parent tool call indefinitely.
Desired behavior
close_agent should not wait forever for a child runtime to finish shutdown.
Suggested behavior:
- submit shutdown as today
- bound the wait for session-loop termination
- if the timeout fires, return a structured timeout/error to the parent instead of blocking indefinitely
- do not remove/release/forget the child if termination was not observed, so the still-live thread remains inspectable/retryable and its slot is not falsely freed
- normal completed shutdown should keep existing cleanup behavior
Candidate patch
Because external code contributions are by invitation only, I am filing this as an issue/proposal rather than expecting an unsolicited PR to be accepted.
I pushed a minimal candidate implementation in a public fork for reference:
- branch: https://github.com/alygg77/codex/tree/codex/bound-v1-close-agent-shutdown
- commit:
ca2890159e3e264e2f52599ac5d122ee4f66717f - compare: https://github.com/openai/codex/compare/main...alygg77:codex/bound-v1-close-agent-shutdown?expand=1
The candidate patch:
- adds a bounded v1 close-agent termination wait
- returns the existing request-timeout error on termination timeout
- leaves the live thread tracked on timeout
- adds a deterministic regression test with a child task whose abort hook blocks
Validation on candidate patch
Commands run locally:
cargo fmt --check
cargo test -p codex-core close_agent_times_out_when_child_session_does_not_terminate
cargo test -p codex-core close_agent_submits_shutdown_and_returns_previous_status
RUST_MIN_STACK=8388608 cargo test -p codex-core shutdown_agent_tree_closes
RUST_MIN_STACK=8388608 cargo test -p codex-core tool_handlers_cascade_close_and_resume_and_keep_explicitly_closed_subtrees_closed
RUST_MIN_STACK=8388608 cargo test -p codex-core spawn_agent_releases_slot_after_shutdown
Notes:
cargo fmt --checkpassed, with stable rustfmt warnings about the repo config keyimports_granularitybeing unstable.- On this local macOS/Rust 1.95 setup, some existing agent-control tests stack-overflow without
RUST_MIN_STACK; I verified upstreammainshows the same stack overflow forspawn_agent_releases_slot_after_shutdown, so that appears pre-existing in this environment rather than introduced by the candidate patch.
Why this matters
Cleanup should not be able to consume hours of wall-clock time after useful work is done. Once wait_agent has already timed out repeatedly, calling close_agent should be a bounded cleanup operation, not a potentially indefinite parent-turn blocker.
This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗