close_agent can wait indefinitely before session teardown, leaking MCP subprocesses, pipes, and pidfds
Nobody has claimed this yet.
Assessment
- Difficulty
- 5/5
- Estimated time
- Over a week
- Newbie friendliness
- 45/100
Research direction
Start with the close_agent flow through Op::Shutdown and wait_until_terminated, then inspect SessionTask::abort and the turn-lifecycle abort hooks described in the issue. Reproduce the pending-task and stdio MCP scenario, and verify that close_agent has a finite bound, teardown or reliable reaper ownership is established, and current and superseded MCP subprocesses and resources are released.
Written by the indexing model from the issue text.
Description
What version of the Codex App are you using (From “About Codex” dialog)?
0.145.0
What subscription do you have?
Pro
What platform is your computer?
AppServer on Ubuntu 2404
What issue are you seeing?
This is a teardown for #29937
close_agent can block forever while waiting for a child agent’s session loop to terminate. When the child is stuck during task-abort or shutdown lifecycle handling, normal session cleanup is never reached. This leaves the child agent’s session and MCP connection sets alive. Stdio MCP subprocesses, their stdin/stdout/stderr pipes, and associated pidfds consequently remain owned by the long-running app-server process. A timeout that only releases the close_agent caller is insufficient: teardown must either complete or transfer the still-live task, session, transports, and subprocesses to a reliable background reaper.
A several hour long Codex 0.145.0 app-server session executing a detailed plan via /goal reached approximately:
930 open file descriptors against a soft limit of 1024
619 pipe descriptors
207 pidfds
204 MCP-related child processes
Further inspection found:
All 619 pipe descriptors mapped to MCP child stdin, stdout, or stderr.
Most corresponding agent threads had already logically completed or been aborted.
The resources remained held by the long-lived app-server.
Individual close_agent operations had remained pending for hours.
What steps can reproduce the bug?
The problem can be reproduced without relying on a real model request:
- Start a child agent with a real stdio MCP test server.
- Have the server write its PID to a temporary file.
- Install a SessionTask whose run future remains pending.
- Make its task-abort callback remain pending after signalling that it was entered.
- Call close_agent.
- Wait longer than the intended shutdown bound.
- Inspect the child PID and thread-manager state.
Against the affected upstream code:
- close_agent does not return.
- The task-abort callback has been entered.
- The agent thread remains registered.
- The MCP subprocess remains alive.
- Its stdio descriptors remain held by the app-server.
A second variant can block a turn-lifecycle abort hook instead of SessionTask::abort; it produces the same outcome.
A useful stronger regression also retains an older MCP binding, refreshes the runtime to create a replacement connection, and then closes the agent. Both the current and superseded MCP subprocesses must eventually exit.
What is the expected behavior?
When close_agent is requested:
- The caller returns within a finite, documented bound.
- Graceful shutdown is attempted.
- If graceful shutdown stalls, the child session is forcibly quiesced or transferred to a reliable reaper.
- Active child tasks are cancelled and joined.
- MCP refresh/startup workers cannot publish new transports during teardown.
- All current and superseded live MCP connection sets are shut down.
- Stdio MCP subprocess termination is initiated under a cancellation-safe owner.
- The thread is removed and agent capacity released only after teardown completes or reliable reaper ownership has been established.
- The operation must not report clean success while the agent remains live and unmanaged.
Additional information
The relevant control flow is effectively:
close_agent
-> send Op::Shutdown
-> wait_until_terminated().await
-> remove thread
-> release residency/capacity
If the session loop does not terminate, wait_until_terminated() never completes. Everything after it is skipped.
During normal shutdown, the session first aborts active tasks. Task-specific SessionTask::abort and lifecycle callbacks are asynchronous and are not intrinsically bounded. A callback that never resolves prevents the session loop from reaching the later MCP and process cleanup stages.
The resulting failure chain is:
close_agent sends Op::Shutdown
-> session begins aborting its active task
-> task abort callback or lifecycle hook hangs
-> session loop never reaches terminated state
-> close_agent waits indefinitely
-> thread removal and capacity release never run
-> session and MCP runtime remain strongly referenced
-> MCP transports and subprocess handles remain alive
-> child pipes and pidfds accumulate in app-server
Related reports
#17574 : broader per-agent MCP process/resource accumulation
#19092: close could appear successful while MCP processes remained alive
#11324: /exit retained MCP PIDs while teardown through /archive removed them
- Dominant language
- Rust
- Stars
- 125k
- Forks
- 19.5k
- Avg merge
- 1m
- Merged PRs (30d)
- 1k
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from openai/codex
-
enhancement remote
Difficulty 2/5 1-3 hours Newbie friendliness 68/100
-
bug CLI windows-os
Difficulty 2/5 1-3 hours Newbie friendliness 76/100
-
macOS sandbox blocks hw.optional.arm64 sysctl, causing Flutter to misdetect Apple Silicon as x64 Openbug CLI sandbox
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
-
bug CLI TUI
Difficulty 2/5 1-3 hours Newbie friendliness 90/100
-
CLI config enhancement skills
Difficulty 2/5 1-3 hours Newbie friendliness 84/100
Similar issues
-
Difficulty 2/5 1-3 hours Newbie friendliness 86/100
kwakseongjae/auto-hwp#319 ·
-
area:cli bug filter-quality good first issue priority:medium
Difficulty 2/5 1-3 hours Newbie friendliness 84/100
-
Difficulty 1/5 Under an hour Newbie friendliness 72/100
bevyengine/bevy#25861 ·
-
comp-datalake
Difficulty 2/5 1-3 hours Newbie friendliness 88/100
ClickHouse/ClickHouse#121222 ·
-
A-linter
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
oxc-project/oxc#26863 ·