Filing separately from the closed #18881 because the shutdown path that PR #19753
fixed now works on this build, while accumulation during a live session does
not. Cross-referencing #12491 (open, GUI), #20349, #25015, #26984.
Environment
|
|
| Codex (ChatGPT.app) |
26.810.52044, bundle 6662 |
| codex-cli |
0.148.0-alpha.9 |
| macOS |
26.6.1 (25G76), arm64 |
| Surface |
Codex desktop app, codex ... app-server |
Two stdio MCP servers configured in ~/.codex/config.toml, both launched via a
wrapper script that execs (so the wrapper leaves no process of its own):
[mcp_servers.fin]
command = "/Users/<user>/.ai_ops/bin/mcp-launch.sh"
args = ["fin"] # execs a python stdio server
[mcp_servers.email]
command = "/Users/<user>/.ai_ops/bin/mcp-launch.sh"
args = ["email"] # execs `npx -y @codefuturist/email-mcp stdio`
What happens
A long-lived app-server spawns a fresh stdio MCP server roughly every three
minutes and never closes the previous one. The old servers stay parented to the
app-server with their stdio pipes still held open (lsof shows fds 0/1/2 as
PIPE), at 0.0% CPU and ~1s of accumulated CPU time. They are idle but retained.
This is the same shape as #18881, but that issue was closed by PR #19753
(merged 2026-04-28) and this build postdates it.
What PR #19753 did fix, verified here
Shutdown draining works. When the app-server exits, its MCP children go with it.
Observed directly: app-server PID 43065 was restarted, and all ~33 of its
accumulated MCP children terminated along with it, requiring no manual cleanup.
So this is not a regression of the shutdown path. It is the in-session path,
where servers are replaced but the superseded ones are never shut down.
Measurements
Two independent windows, one app-server each, machine otherwise idle:
|
|
| Rate |
~1 new stdio server per 3 minutes, sustained |
| Single app-server (PID 14353), 71 min uptime |
24 fin_mcp children |
| Across both configured servers, ~1 hour after a manual clear from 11 processes |
137 processes |
| RSS at that point |
~5.4 GB |
| Free system memory at that point |
141 MB |
| After killing the superseded children |
4480 MB free |
Reproduced twice, hours apart, across an app-server restart in between.
Reproduction
- Configure one or more stdio MCP servers in
~/.codex/config.toml.
- Start the Codex desktop app and leave a project session open.
- Watch the children of the
app-server process:
APPSRV=$(pgrep -f "Resources/codex .*app-server" | head -1)
watch -n 30 "pgrep -P $APPSRV -f mcp | wc -l"
The count climbs monotonically and never decreases while the app-server lives.
Expected
When a stdio MCP server is replaced, the superseded one is shut down and its
process reaped, so the count tracks the number of configured servers rather than
session age.
Notes
Each server is cheap alone (~28 to 90 MB here) and the fault is only visible over
hours. With two servers configured it took roughly one hour to consume several GB
and drive the machine to 141 MB free. #12491 reports the same end state at much
larger scale (1319 processes, 37 GB), which suggests the ceiling is however long
the app stays open.
The three fix directions proposed in #12491 (process groups, startup reaping of
stale trees, heartbeat self-termination) would each cover this case too. The
narrowest fix specific to what is seen here is shutting down the superseded
server at the point of replacement, rather than only at session shutdown, which
is what #19753 addressed.
Filing separately from the closed #18881 because the shutdown path that PR #19753
fixed now works on this build, while accumulation during a live session does
not. Cross-referencing #12491 (open, GUI), #20349, #25015, #26984.
Environment
codex ... app-serverTwo stdio MCP servers configured in
~/.codex/config.toml, both launched via awrapper script that
execs (so the wrapper leaves no process of its own):What happens
A long-lived
app-serverspawns a fresh stdio MCP server roughly every threeminutes and never closes the previous one. The old servers stay parented to the
app-server with their stdio pipes still held open (
lsofshows fds 0/1/2 asPIPE), at 0.0% CPU and ~1s of accumulated CPU time. They are idle but retained.
This is the same shape as #18881, but that issue was closed by PR #19753
(merged 2026-04-28) and this build postdates it.
What PR #19753 did fix, verified here
Shutdown draining works. When the app-server exits, its MCP children go with it.
Observed directly: app-server PID 43065 was restarted, and all ~33 of its
accumulated MCP children terminated along with it, requiring no manual cleanup.
So this is not a regression of the shutdown path. It is the in-session path,
where servers are replaced but the superseded ones are never shut down.
Measurements
Two independent windows, one app-server each, machine otherwise idle:
fin_mcpchildrenReproduced twice, hours apart, across an app-server restart in between.
Reproduction
~/.codex/config.toml.app-serverprocess:The count climbs monotonically and never decreases while the app-server lives.
Expected
When a stdio MCP server is replaced, the superseded one is shut down and its
process reaped, so the count tracks the number of configured servers rather than
session age.
Notes
Each server is cheap alone (~28 to 90 MB here) and the fault is only visible over
hours. With two servers configured it took roughly one hour to consume several GB
and drive the machine to 141 MB free. #12491 reports the same end state at much
larger scale (1319 processes, 37 GB), which suggests the ceiling is however long
the app stays open.
The three fix directions proposed in #12491 (process groups, startup reaping of
stale trees, heartbeat self-termination) would each cover this case too. The
narrowest fix specific to what is seen here is shutting down the superseded
server at the point of replacement, rather than only at session shutdown, which
is what #19753 addressed.