Repository navigation
MCP server processes are re-spawned on every session/thread context and never reaped (Windows) #44683
Description
MCP server processes are re-spawned on every session/thread context and never reaped (Windows)
Summary
Every time Codex establishes a new session/thread context, it spawns a complete fresh set of all configured MCP server processes. The previously spawned sets are never terminated. They stay alive and idle for the entire lifetime of the Codex process, accumulating without bound.
In a 4.7-hour period on an otherwise idle machine this produced 35 duplicate sets of every configured MCP server — 464 leaked processes consuming 6.9 GB of RAM, with total system process count climbing from ~500 to 967.
Environment
- OS: Windows 11 Pro 10.0.26200 (x64)
- Codex CLI:
0.153.4 - Sandbox helper observed:
codex-command-runner-0.153.4.exe - Machine: 32 logical CPUs, 93 GB RAM
- 4 MCP servers configured (3× Node-based, 1×
uvx-based)
Symptom
MCP server process counts grow monotonically for as long as the Codex process lives. Growth continues while Codex is completely idle (main process measured at ~0% CPU, no task running, no user interaction).
Observed growth of one MCP server (node-based), sampled hourly by an external monitor:
10:48 33 (+17)
11:48 52 (+19)
12:48 72 (+20)
13:48 97 (+25)
Evidence
1. All configured MCP servers accumulate in perfect lockstep
mcp_server_a (node) 35 sets first 09:44:58 last 14:24:12
mcp_server_b (node) 35 sets first 09:44:58 last 14:24:12
mcp_server_c (node_repl) 35 sets first 09:44:58 last 14:24:12
mcp_server_d (uvx) 35 sets first 09:44:58 last 14:24:12
Four servers, four completely different executables and runtimes, identical counts and identical timestamps. A crash-loop in any single server cannot produce this. The only explanation is that the whole MCP group is being re-initialised as a unit.
2. Session rollout files line up with process spawn times, to the second
Comparing sessions/<date>/rollout-*.jsonl creation times against MCP process start times:
rollout file 09:44:47 -> MCP set spawned 09:44:58
rollout file 11:45:12 -> MCP set spawned 11:45:12 <- same second
rollout file 13:00:18 -> MCP set spawned 13:00:18 <- same second
rollout file 13:24:05 -> MCP set spawned 13:23:03 / 13:24:05
So the trigger is establishing a session context.
Note: only 6 rollout files existed that day, but 35 sets accumulated — so session creation is not the only trigger. Something else (session resume, config reload, window/thread switch) also re-spawns the group. Spawn intervals were irregular: 5–40 min apart, with occasional pairs 0.1–1.0 min apart.
3. Old sets are never terminated
All 35 sets were still alive and parented to the same Codex PID. The oldest set had been running 4.7 hours. None had exited, none were reaped on session switch or session close.
4. Cost per set is multiplied by process trees
A uvx-launched MCP server expands into a 5-process chain:
uvx.exe -> uv.exe (50 MB) -> <server>.exe -> python.exe -> python.exe (68 MB)
35 sets of that one server alone = 175 processes. It accounts for roughly a third of all leaked processes.
Worse: that particular MCP server connects to an external application that was not running at all. All 175 processes were spinning with no reachable target for the entire session.
Reproduction
- Configure 2+ MCP servers in
config.toml(the more per-server child processes, the more visible). - Start Codex, leave it idle.
- Create/switch session threads over a few hours.
- Watch process count:
Get-Process | Where-Object { $_.Parent... }, or group by parent PID.
Expected: one live set of MCP servers.
Actual: one set per session context, none reaped.
Impact
- Unbounded process and memory growth over a long-lived Codex session (measured: 464 processes / 6.9 GB in under 5 hours).
- Second-order cost: on Windows, several system components enumerate the full process table periodically (WMI
Win32_Processsubscribers, security software, search indexers). Their cost scales linearly with process count, so this leak inflates CPU usage well beyond the leaked processes' own consumption. - Idle MCP servers that cannot reach their target keep TCP/stdio handles open indefinitely.
Workarounds currently used
- Restart Codex periodically.
- Remove expensive MCP servers from
config.toml(disabling theuvx-based one cut the per-set process count from 9 to 4, roughly halving the leak rate). - External monitor that alerts when the count of same-named children under one parent grows past a threshold, then kill all but the newest set. Killing the stale sets does not disturb the running Codex session.
Suggested fix
Reap the previous MCP server set when a session context is torn down or replaced, or reuse a single long-lived MCP server pool across session contexts instead of spawning per-context. Child process trees need to be terminated as a group — killing only the direct child leaves the uv/python grandchildren orphaned.
Activity
github-actions commentedon Sep 11, 2026
Potential duplicates detected. Please review them and close your issue if it is a duplicate.
Powered by Codex Action
CoolCollin commentedon Sep 11, 2026
Closing as a duplicate of #38754 (and closely related to #35485 / #18881).
Same failure mode, same platform. I've moved the two pieces of evidence that weren't already in those threads — the lockstep accumulation across four unrelated MCP runtimes, and the session rollout timestamps lining up with spawn times to the second — into a comment on #38754 so the discussion stays in one place.