Windows Desktop: bundled node_repl MCP processes leak one-per-thread, never reaped until app-server exit
What version of Codex is running?
- Codex Desktop
26.721.4979.0(native Windows, no WSL) - app-server:
codex-cli 0.146.0-alpha.3.1 - Bundled runtime:
cua_node/f8d2abcb7481383b
What operating system are you using?
Windows 11 x64, 24 GB RAM.
What is the issue?
node_repl.exe MCP server child processes accumulate over the life of the codex.exe app-server process and are never reaped. One new node_repl.exe appears roughly per opened conversation/thread, stays alive after the thread is idle or closed, and only goes away when the app-server itself exits.
Snapshot after ~8 hours of normal Desktop usage — 14 live node_repl.exe, all children of the single app-server PID, all idle (0 CPU since spawn):
ProcessId CreationDate Parent
17248 12:41 (app start) codex.exe (1772)
16888 13:12 codex.exe (1772)
19040 13:43 codex.exe (1772)
5508 15:18 codex.exe (1772)
21312 18:40 codex.exe (1772)
1668 19:02 codex.exe (1772)
21144 19:47 codex.exe (1772)
9004 19:58 codex.exe (1772)
20704 19:58 codex.exe (1772)
21984 20:04 codex.exe (1772)
20664 20:12 codex.exe (1772)
14232 20:18 codex.exe (1772)
20508 20:18 codex.exe (1772)
20588 20:19 codex.exe (1772)
Each holds ~7–9 MB working set and ~115–125 handles. Individually small, but they accumulate monotonically (verified creation timestamps map to thread-open events) and add up alongside the other per-thread state. Killing the app-server reaps all of them (verified: after terminating the app-server PID, node_repl.exe count went from 14 to 0).
Relevant config: [mcp_servers.node_repl] pointing at the bundled node_repl.exe, features.computer_use = true, features.multi_agent = true.
What is the expected behavior?
node_repl MCP processes for a thread should be terminated when the thread is closed/idle-expired, or at minimum pooled/reused, instead of leaking one process per thread until app exit.
Additional context
Possibly related to #30408 (per-thread MCP server processes never cleaned up), but this one is specifically the bundled node_repl on native Windows, where each thread spawns a distinct child that outlives the thread.
7 Comments
Potential duplicates detected. Please review them and close your issue if it is a duplicate.
Powered by Codex Action
Current Windows reproduction and lifecycle analysis
I reproduced the same process-retention pattern on a newer Windows Desktop build:
26.730.7989.0codex-cli 0.147.0-alpha.1.2At an earlier point in the same app-server lifetime I observed 7 direct
node_repl.exechildren using about 143.5 MiB combined. A fresh read-only capture at2026-08-05T13:56+09:00showed 4 direct children using 81.3 MiB and 532 handles. Three of the four used at most 0.02 CPU seconds during a three-second sample; one was active and owned a nested stdio app-server.This is an important distinction: these are not Windows PPID-orphans. They remain direct children of the live Desktop app-server (and were inside its Job in the earlier snapshot). Also, my count did decrease from 7 to 4 during the investigation, so my reproduction supports long-lived/logically retained runtimes, but does not support claiming that every instance is literally never reaped under every lifecycle path. Process inspection alone also cannot attribute every retained
node_repl.exeto a particular thread, status snapshot, hidden task, or other runtime owner.Relevant current-main behavior
I traced current
mainat5d89ab65dc9d4d0c55796c11df112b54157922b4.The per-thread path behaves as follows:
https://github.com/openai/codex/blob/5d89ab65dc9d4d0c55796c11df112b54157922b4/codex-rs/app-server/src/request_processors/thread_lifecycle.rs#L5-L62
thread/unsubscriberemoves the connection subscription but does not immediately shut down the thread:https://github.com/openai/codex/blob/5d89ab65dc9d4d0c55796c11df112b54157922b4/codex-rs/app-server/src/request_processors/thread_processor.rs#L887-L912
mcp_runtime.shutdown():https://github.com/openai/codex/blob/5d89ab65dc9d4d0c55796c11df112b54157922b4/codex-rs/core/src/session/handlers.rs#L587-L609
There is also a separate RPC/temporary-manager path that should not be conflated with thread subscription retention:
mcpServerStatus/list, resource reads, and tool calls withtokio::spawn:https://github.com/openai/codex/blob/5d89ab65dc9d4d0c55796c11df112b54157922b4/codex-rs/app-server/src/request_processors/mcp_processor.rs#L234-L305
https://github.com/openai/codex/blob/5d89ab65dc9d4d0c55796c11df112b54157922b4/codex-rs/app-server/src/request_processors/mcp_processor.rs#L391-L480
McpConnectionSet, then cancels its startup token and returns without explicitly awaitingshutdown():https://github.com/openai/codex/blob/5d89ab65dc9d4d0c55796c11df112b54157922b4/codex-rs/codex-mcp/src/mcp/mod.rs#L393-L451
taskkill /PID ... /T /F, but current code discards the command status:https://github.com/openai/codex/blob/5d89ab65dc9d4d0c55796c11df112b54157922b4/codex-rs/rmcp-client/src/stdio_server_launcher.rs#L373-L386
The evidence therefore points to at least two plausible retention mechanisms:
The first mechanism is consistent with the controlled subscribed-thread analysis reported in #20883:
https://github.com/openai/codex/issues/20883#issuecomment-4976823228
Relevant prior work that was not merged
Three earlier PRs overlap materially with this issue and should be evaluated before designing a new fix:
taskkillfailures, and added serialization coverage. It was closed as stale without being merged:https://github.com/openai/codex/pull/20447
https://github.com/openai/codex/pull/14997
https://github.com/openai/codex/pull/15258
These PRs target different layers, so none should be assumed to be a complete standalone fix for the current architecture. In particular, #20447 addresses detached temporary-manager/RPC lifetime, while #14997 and #15258 address per-thread retention and duplication.
Proposed fix direction
My preferred order would now be:
mcp_processor.rs:thread/closed, and preserve resume from the persisted rollout.Regression coverage should include:
The last item is currently a coverage gap: the existing real-process cleanup integration test is Unix-only:
https://github.com/openai/codex/blob/5d89ab65dc9d4d0c55796c11df112b54157922b4/codex-rs/rmcp-client/tests/process_group_cleanup.rs#L1-L119
Would maintainers prefer reviving the narrow #20447 lifecycle fix first, a Desktop subscription fix, or a combined staged approach? If one of those directions aligns with the intended lifecycle, I would be happy to prepare a small, test-first PR if invited.
Additional Windows reproduction on current Desktop
26.810.7004.0, with read-only lifecycle/performance measurements.This matches the lifecycle nuance already described above: the helpers are not literally never reaped. On this machine, a true idle period of roughly 30 minutes reclaimed most of a retained batch (
28 -> 7; 22/28 baseline helper PIDs exited). However, under sustained multi-thread Desktop use, helper creation outpaced that delayed cleanup and the process tree accumulated rapidly.Environment / current build
26.810.7004.026.810.52044Helper lifecycle observations
Across normal thread/task activity:
node_repl.exe:4 -> 6 -> 10 -> 12 -> 22 -> 23 -> 26 -> 2528 -> 7codex.execodex.exechildrenrmcp::service/rmcp::transport::child_processplus occurrences involvingfailed to terminate,failed to kill,orphan, andcleanupA particularly narrow trigger was observed: resuming another thread and asking only a design/conversation question (no requested local file, shell, browser, or computer action) increased
node_repl.exefrom10 -> 12. Previously observed helpers were not reclaimed at that point. This suggests helper/runtime creation can occur during thread/work-context initialization, not only after an explicit tool call.Performance degradation while active workload was running
During a later sustained-workload bad state, the root
codex.exeshowed a persistent cache-backed logical-read storm across three consecutive 5-second windows:265.752 MiB/s288.004 MiB/s286.842 MiB/s280.2 MiB/s288.0 MiB/s34k-37k/sAt the same time:
0-0.079 MiB/s0.24%011.04%, max19.36%41.7 GiB33.5%0.13%181,208/saverage, ~200,951/speaknode_repl.exe: idle baseline7, then22 -> 23after work resumedcodex.exeprivate memory grew from ~992 MiB -> 2263 MiBin ~10 minutes, with large short-window allocation/free churnUser-visible impact was system-wide mouse/input/window-animation stutter despite low total CPU, essentially idle physical disk, ample RAM, and low DPC activity. Fully exiting/restarting Codex cleared the bad state temporarily.
File-level trace follow-up
An 8.139-second PID-filtered kernel File I/O trace was later captured after the storm had subsided:
0799/3.297 MiBtotal (0.405 MiB/s)%USERPROFILE%\.codex\logs_2.sqliteandlogs_2.sqlite-wal, read in short ~2-second burstsThat calm-state trace did not reproduce the prior ~280 MiB/s process-level logical-read storm, so it does not attribute the storm to those SQLite files. The high-read state appears state-dependent/intermittent and would need another trace captured while the stutter is actively present.
Current interpretation
This machine supports:
codex.exelogical-read activity and a ~180k-200k/s context-switch storm;26.810.7004.0here.I am intentionally not uploading the raw ETL because it contains local workstation metadata/paths. Sanitized counters or another trace captured during the active bad state can be provided if maintainers need a specific additional measurement.
Additional Windows Desktop sample, non-urgent:
Counts declined throughout the sample, so this is not urgent or evidence of current growth. It still supports the lifecycle issue: child instances persist across task or tool activity rather than being retired per thread. No local process termination was used for this sample; the user will close and relaunch the app normally.
Reproduced on a newer Windows Desktop build; this issue is still present.
Directly measured environment
10.0.19045(build 19045), x64OpenAI.Codex_26.814.5167.0_x64__2p2nqsd0c76g010976cua_node/2fb562745e6d66f0/bin/node_repl.exeObserved behavior
All observed
node_repl.exeinstances are direct children of the same long-livedcodex.exe app-server.node_repl.exeprocessesThis confirms the leak/retention also reproduces on Windows 10 and on Desktop build
26.814.5167.0, not only the older Windows 11 build reported in the original issue.The same user separately observes a more severe reproduction on a Windows 11 home PC: the count reportedly increases on every chat turn. Disabling
node_repl.exeprevents the accumulation, but Browser/Web work and Computer Use then stop functioning, so disabling the runtime is not a viable workaround.Expected
The process count should remain bounded through reuse/pooling, or thread-scoped runtimes should be reaped after the owning turn/thread becomes idle. Long-lived Desktop sessions should not monotonically accumulate
node_repl.exechildren.A full app-server restart remains the only reliable cleanup observed.
Additional sanitized Windows observation from newer builds:
codex.exeprocess had 11 direct bundlednode_repl.exechildren.cmdprobes from the task failed before launch with Win32 error 1816 (ERROR_NOT_ENOUGH_QUOTA).The direct
codex.exe -> node_repl.exeshape appears related to this report, but this snapshot does not map a child to a specific open or closed thread and therefore does not prove missed cleanup. Is there a supported diagnostic field or procedure for correlating each bundlednode_replchild with its owning thread/session and terminal cleanup event?Additional native Windows Desktop reproduction on
OpenAI.Codex 26.818.8289.0. This measurement is from the Desktop app-server, not the VS Code extension.Read-only capture at
2026-08-26T00:21:49Z:codex.exe app-server, started at2026-08-25T15:50:19Z;node_repl.exeprocesses, all direct children of that app-server;node_repl.exeworking set: approximately 110 MiB;No processes were terminated.
This is a point-in-time persistence/accumulation snapshot. It confirms the direct parent-child shape on the current native Desktop build, but it does not map each child to a specific thread or prove that every individual child missed its cleanup deadline.
For a narrow first fix, I would favor re-evaluating the lifecycle intent of #20447 on current main before attempting MCP pooling:
shutdown().awaittemporaryMcpConnectionSetowners on success, error, and cancellation;The subscribed-thread retention path can then be handled separately: Desktop should unsubscribe background threads it no longer needs, and idle unload must protect active turns and pending requests while preserving resume from the rollout. Project/app-server MCP pooling seems better treated as a later architecture change, not a substitute for deterministic teardown.
Is there a supported diagnostic field or procedure for mapping each bundled
node_replchild to its owning thread/request and terminal cleanup event? I can provide the sanitized health JSON or run a controlled create/close/reap correlation test if maintainers specify the intended procedure.