Windows: codex mcp-server / app-server and their child MCP servers are never reaped (accumulate to hundreds over time)
Summary
On Windows, using Codex through its MCP-server path (codex mcp-server, which a host like Claude Code launches) leaks processes: every request spawns a new codex app-server, which in turn spawns the MCP servers from ~/.codex/config.toml — and none of them are reaped when the request/session ends. Over a few days of normal use they accumulate into the hundreds and noticeably degrade the machine.
codex exec (one-shot) does not have this problem — it spawns the same children but cleans them up on normal exit. The leak is specific to the persistent mcp-server/app-server path on Windows.
Environment
- OS: Windows 11
- codex-cli: 0.139.0 (model gpt-5.5)
- Configured MCP servers in
~/.codex/config.toml: playwright, comfyui, node_repl (any configured stdio MCP servers reproduce this)
Observed behavior (controlled A/B, measured with Get-CimInstance Win32_Process)
- Fresh reboot baseline: 1
codex mcp-server, 0 child MCP processes. - One request through the MCP server: +1
codex app-server, and +1 of each configured MCP server (each as acmd → npx → nodechain). After the request completes, none of these exit. - After a few days / many sessions: ~800+ lingering
node/cmd/codexprocesses. Parent analysis: only ~3 were true orphans; ~835 still had live parents — piled up under 2 long-livedcodex app-serverinstances (52–56h old), i.e. children accumulating under persistent app-servers rather than being cleaned up.
Contrast: codex exec is clean
Running the equivalent work via codex exec --ephemeral ... - spawns the same MCP children during the run, but all of them return to baseline on normal exit (verified). So the runtime can tear them down — the persistent mcp-server/app-server path on Windows doesn't.
Possibly related
codex app-server daemon <...> reports: app-server daemon lifecycle is only supported on Unix platforms. This suggests Windows app-server lifecycle management is less complete, which may be why child trees aren't reaped on Windows.
Repro
- On Windows, configure one or more stdio MCP servers in
~/.codex/config.toml. - Drive Codex through
codex mcp-server(e.g. via an MCP host) and issue several requests. - Watch
codex.exe,node.exe, and the MCP server processes via Task Manager /Get-CimInstance Win32_Process— they accumulate and never exit.
Impact
Resource exhaustion (hundreds of processes / multiple GB) over long-lived sessions on Windows.
Suggested direction
Reap each app-server and its child MCP process tree when the request/session ends (or on mcp-server shutdown), including a Windows tree-kill (e.g. taskkill /pid <pid> /t /f) since Windows does not cascade-terminate child processes on parent exit.
_(Searched existing issues for duplicates; didn't find an obvious match.)_
6 Comments
Potential duplicates detected. Please review them and close your issue if it is a duplicate.
Powered by Codex Action
The useful split in this repro is
codex execclean vscodex mcp-serverleaking on the same configured stdio MCP servers. That makes this look less like an individual MCP server shutdown bug and more like ownership of the child process tree in the persistent app-server path.One acceptance test that would make the fix hard to regress: start
codex mcp-serveron Windows with two trivial stdio MCP servers, send one request through an MCP host, wait for the request/session to finish, then assert that the descendantcodex app-server,cmd,npx, andnodeprocesses return to the pre-request count. I would also keep thecodex exec --ephemeralcase in the same test as a control, since it proves teardown is possible for the same config.The duplicate check should probably distinguish true orphans from still-parented descendants too. The report's "~835 still had live parents" detail is important; a cleanup that only kills orphaned children would miss the main failure mode here.
Confirming on codex-cli 0.144.1, Windows 11, plain interactive CLI sessions (not the
codex mcp-serverpath) — and adding a pattern that may help narrow it down.Generation pattern per subagent/reconnect wave
With 8 stdio MCP servers in
~/.codex/config.toml, each interactive session re-spawns a complete set of all configured servers as a "generation" on what appears to be subagent/reconnect boundaries, without closing the previous generation. Measured viaGet-CimInstance Win32_Process:codex.exePIDThe abandoned generations are connected-but-silent: their stdin pipes are never closed (no EOF reaches the child), they receive no further MCP traffic, and they stay alive indefinitely. So this reproduces with live, healthy parents — it is not only an exit/reap problem; old generations are abandoned mid-session.
Server-side mitigation that works (for MCP server authors)
We patched our own stdio servers to self-terminate, which fully drains the leak for servers you control:
process.kill(ppid, 0)liveness + a creation-time identity fingerprint (covers hard kills; Windows never reaps orphans)<SERVER>_IDLE_EXIT_MIN=120set only in codex's[mcp_servers.X.env]blocks): the server exits after N minutes without any incoming MCP message, and codex transparently respawns it on next use. This is the only signal that catches the live-parent abandoned-generation class.Third-party servers can't be patched this way, so an upstream fix (close the previous generation's pipes when spawning a replacement, and/or Job Objects with
JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSEon Windows) would still be needed.Related: #25015 describes what looks like the same per-subagent stack leak on Linux.
Confirming this reproduces on Linux, which I think matters for the
windows-oslabel and for the "Windows app-server lifecycle is less complete" hypothesis in the original report.Environment
~/.codex/config.tomlcodex mcp-server(launched by a host), several concurrent sessionsSame generation pattern as @NAlle33 reported, larger magnitude
The complete configured set is re-spawned as a "generation" without the previous one being closed. Measured from
/proc:codex mcp-serverPIDcodex mcp-serverPID accumulating 182 child MCP servers over ~3 hourscodex mcp-serverPID is already back to 47 childrenParent analysis matches the original report
This is the detail I want to reinforce, because it defeats the obvious mitigations. Classifying every MCP process by walking
PPidup to its owning session:init, or tosystemd --user): 0 to 18 at any samplecodex mcp-server: hundredsSo
pkill-by-orphan does nothing, and the processes are not reaped by normal parent-death cleanup. They are idle, connected-but-silent, and they get paged out rather than freed.Impact
The abandoned generations end up holding almost entirely swap, not RSS, since they go idle immediately. On this box they drove swap from 0 to 24 GB (100%, fully exhausted) and made the machine unusable, repeatedly. Reaping the abandoned generations reliably returns 12-14 GB.
Caveats on my evidence
Being explicit about confidence, since the original report did a clean A/B and I did not:
codex exec --ephemeralcontrol case on Linux, so I cannot independently confirm the "exec is clean, mcp-server leaks" split here.codex app-serverprocesses do exist on this box, but from the desktop app and the VS Code extension; the accumulating children I measured sit undercodex mcp-serverPIDs.Suggestion
Given a Linux reproduction on 0.147.0, the
windows-oslabel is probably too narrow, and the "app-server daemon lifecycle is only supported on Unix platforms" note may be pointing away from the real cause rather than toward it. The acceptance test @tangweigang-jpg proposed would be worth running on Linux too.Correction to my comment above — one figure I posted was wrong, and I want to fix it before it misleads anyone's debugging.
I said "27 instances of the same MCP server kind spawned within 60 seconds." That number came from a bug in my own measuring tool: its 60-second window compared process ages without guarding the sign, so anchoring on the oldest member made every younger one pass trivially. It was reporting group size, not burst size. The accumulation counts in my comment were direct process counts and are unaffected; only the "within 60 seconds" figure was wrong.
Re-measured with that fixed, the picture is more useful, because it splits into two distinct behaviours rather than one:
| Owner / kind | instances | max in any 60s | span | sustained rate |
|---|---|---|---|---|
|
codex mcp-serverA, remote-bridge server | 501 | 11 | 383 min | ~1.3/min ||
codex mcp-serverB, remote-bridge server | 78 | 12 | 382 min | ~0.2/min ||
codex mcp-serverA, stdio server (chrome-devtools) | 18 | 18 | 1 min | one burst |So on Linux I see:
mcp-remotebridge server — ~1.3 new processes per minute, held for over 6 hours, reaching 501 under one PID. This is the larger share by far: 576 of 660 abandoned processes at last count, ~9.9 GB RSS + 9.0 GB swap.The drip is worth separating out because the obvious explanation does not hold: the remote endpoint is healthy (HTTP 200 in 88 ms when checked mid-pileup), so this is not a reconnect-retry storm against a failing server. Something is creating a new bridge process per interaction and never closing the old one.
Everything else in my previous comment stands: codex-cli 0.147.0 on Ubuntu 24.04, 844 MCP-related processes box-wide at peak, near-zero true orphans with the overwhelming majority still parented to a live
codex mcp-server, and swap driven to full exhaustion.Apologies for the bad number.
Confirming the same live-parent, connected-but-idle generation retention on Linux with the interactive TUI (not
codex mcp-serveror Desktop).Environment
codex-cli 0.149.0Observed process lifecycle
codexparent spawned four complete generations of the same configured server set over approximately 88 seconds.codexparent; none was reparented or orphaned.This is therefore the same abandoned-generation class described above, now reproduced in the direct interactive TUI on 0.149.0. A bounded result would retain MCP runtimes only for genuinely active threads, or detach/shut them down for inactive/completed threads and recreate them lazily when the thread resumes.