Repository navigation
Windows Desktop: bundled node_repl MCP processes leak one-per-thread, never reaped until app-server exit #35485
Description
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.
Activity
github-actions commentedon Jul 26, 2026
Potential duplicates detected. Please review them and close your issue if it is a duplicate.
Powered by Codex Action
misosiruda commentedon Aug 5, 2026
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:
codex/codex-rs/app-server/src/request_processors/thread_lifecycle.rs
Lines 5 to 62 in 5d89ab6
thread/unsubscriberemoves the connection subscription but does not immediately shut down the thread:codex/codex-rs/app-server/src/request_processors/thread_processor.rs
Lines 887 to 912 in 5d89ab6
mcp_runtime.shutdown():codex/codex-rs/core/src/session/handlers.rs
Lines 587 to 609 in 5d89ab6
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:codex/codex-rs/app-server/src/request_processors/mcp_processor.rs
Lines 234 to 305 in 5d89ab6
codex/codex-rs/app-server/src/request_processors/mcp_processor.rs
Lines 391 to 480 in 5d89ab6
McpConnectionSet, then cancels its startup token and returns without explicitly awaitingshutdown():codex/codex-rs/codex-mcp/src/mcp/mod.rs
Lines 393 to 451 in 5d89ab6
taskkill /PID ... /T /F, but current code discards the command status:codex/codex-rs/rmcp-client/src/stdio_server_launcher.rs
Lines 373 to 386 in 5d89ab6
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:
#20883 (comment)
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:Fix MCP status/list lifecycle leak #20447
[app-server] Reap orphaned idle threads on websocket disconnect #14997
[MCP caching]: pool MCP backends in thread manager #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:
codex/codex-rs/rmcp-client/tests/process_group_cleanup.rs
Lines 1 to 119 in 5d89ab6
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.
vbzgn2rc98-ctrl commentedon Aug 17, 2026
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.
wakasellahi commentedon Aug 17, 2026
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.
uto96322 commentedon Aug 18, 2026
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.
grtninja commentedon Aug 24, 2026
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?twk0672005 commentedon Aug 26, 2026
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.Rober1208 commentedon Oct 5, 2026
Same here on Windows: each conversation opened in the desktop app keeps its own
node_repl.exe, together with the rest of its MCP set, until the app server exits.Until there is a fix, I made a small tool that lists these per-conversation copies, with the conversation title, memory and idle time, and stops the idle ones: mcp-janitor
By default it keeps the servers of the conversation you used last (Codex cannot restart a stopped one), and
stop --idle 1h --dry-runshows what it would stop without stopping anything. Reports from other Windows setups would help a lot; please open an issue if it misses or mislabels something.rumours86 commentedon Oct 14, 2026
Same on Linux with the desktop app (
chatgpt26.1007.21434 .deb, Linux Mint 22.3 / Ubuntu 24.04 base, kernel 7.0).One
codex app-server(started by the app ascodex -c features.code_mode_host=true app-server --analytics-default-enabled -c plugins.codex-app-tools@openai-bundled.mcp_servers.codex_app.enabled=true -c plugins.code-review@openai-bundled.mcp_servers.code-review.enabled=true ...)78 minutes after the app was started:
cua_node/bin/node ./server.mjscua_node/.../@oai/cua-repl/bin/cua-repl.mjs(each with its ownnode_replchild)cua_node/bin/node_repl(direct)Together these hold 682 MB (PSS + swap), 590 MB of it in processes older than 30 minutes that use no CPU.
The same app-server at 238 minutes: 16 ×
server.mjs(14 older than 30 min), 8 ×cua-repl(7), 8 ×node_repl(7) - 994 MB, 871 MB of it in the old idle ones. It only grows. They are direct children of the live app-server, not orphans, so they disappear only when the app quits. On a 16 GB laptop this regularly pushes the system into zram reclaim; the same pattern was described in #46833 (Windows) and #39816 (macOS).A new
server.mjs/cua-replpair appears for almost every new thread and is never shut down when the thread finishes or is left idle. An idle timeout for a thread's MCP set (or tearing it down when the thread is unloaded/archived) would fix this on all three platforms.