Repository navigation
[Windows 26.1007] One stdio MCP process set per thread; 13 sets / 353 processes / ~9.6 GiB under a single app-server, reclamation lags creation #53285
Description
What version of the Codex App are you using (From "About Codex" dialog)?
26.1007.21434 (Windows, MSIX). The bundled app-server reports codex-cli 0.162.0-alpha.17.2.
What subscription do you have?
ChatGPT sign-in inside the Codex desktop app (the desktop app does not expose the plan tier in local state that I can read).
What platform is your computer?
Microsoft Windows 11 (10.0.26200), x64.
What issue are you seeing?
Codex Desktop keeps one complete stdio MCP process set alive per thread under a single long-lived codex.exe app-server. The sets accumulate while I work across chats, so the app-server descendant tree grows into the hundreds of processes and multi-GB working set during a session, and is only partially reclaimed.
This is already well documented in other reports, so this report is deliberately limited to two things I could not find covered elsewhere:
- A cheap, exact way to map each MCP process set to its owning thread, using the per-thread lock files.
- Numbers from the newest build (26.1007.21434 / codex-cli 0.162.0-alpha.17.2), including the observed balance between set creation and reclamation, and the per-set cost of npx/uvx based servers.
Environment / capture
- One
codex.exe ... app-serverprocess; uptime about 19 h at the peak capture. - 6 stdio MCP servers are configured, so each set is large:
node_repl(bundled)cua_repl_proxy(python launcher -> node cua-repl.mjs -> node_repl.exe)memory(npx -y @modelcontextprotocol/server-memory)git(uvx mcp-server-git)- plugin-provided
code-review(server.mjs) - plugin-provided
codex-app-tools(server.mjs)
- Each set is roughly 14 processes, because the
npxanduvxbased servers add 4-5 wrapper processes each (cmd.exe -> npx.cmd -> node;uvx -> uv -> python -> *.exe).
Evidence 1: each MCP process set maps 1:1 to a per-thread lock file
%USERPROFILE%\.codex\thread-writer-locks\ contains one zero-byte <thread-id>.lock per thread. On this machine the mtime of each lock file matches the creation time of a whole MCP process set to the second, and each set is a direct child tree of the single app-server.
| thread id (truncated) | lock mtime | MCP set created |
|---|---|---|
| 01a11ca3-...-e8c5610addb2 | 2026-10-11 23:03:55 | 2026-10-11 23:03:55 |
| 01a1251d-...-14e11e6e1458 | 2026-10-12 01:25:17 | 2026-10-12 01:25:17 |
| 01a12c18-...-8c39bb9fef98 | 2026-10-12 01:52:43 | 2026-10-12 01:52:44 |
| 01a12c26-...-74fe05032541 | 2026-10-12 02:08:00 | 2026-10-12 02:08:02 |
| 01a12bf8-...-234f0da600b7 | 2026-10-12 02:26:26 | 2026-10-12 02:26:27 |
This gives a deterministic process-set -> thread mapping with no extra instrumentation; 13 sets matched 13 distinct threads at peak. If it holds generally, it may be a useful ownership diagnostic for this class of report.
Evidence 2: peak footprint and partial reclamation
Peak observed: 13 live sets under one app-server.
- app-server descendant tree: 353 processes, about 9.6 GiB summed working set (this double counts shared pages), all near 0 CPU.
- per-MCP peak instance counts:
mcp-server-git13,server-memory53,node_repl39,cua-repl26,server.mjs42.
After roughly 9 hours without touching those chats, the same app-server had released part of it:
mcp-server-git13 -> 5,node_repl39 -> 15,server-memory53 -> 21,cua-repl26 -> 11.- A later capture during normal use: 7 sets, 195 processes, about 3.9 GiB.
So a delayed idle reclamation does exist (consistent with the documented 30-minute no-subscriber idle unload), but creation outpaces it: every new or resumed thread adds a full set, while a set is only released about 30 minutes after its thread is both inactive and unsubscribed, and visible chats appear to stay subscribed for the whole app session.
logs_2.sqlite shows per-thread teardown hours after creation, for example clearing thread listener during thread-state teardown thread_id=... had_active_turn=false; I did not observe no subscribers and is idle; shutting down entries within the retained log window.
Expected behavior
Either pool stdio MCP servers at app-server or project scope, or eagerly tear down a thread's MCP set once the thread is unsubscribed and idle, so the process count stays bounded instead of growing with the number of chats touched during a session.
Related existing reports
I searched before filing. This overlaps with #35485 (node_repl one-per-thread), #40579 (process-batch accumulation), #20883 (project-scoped pooling) and #30408 (per-thread, 9+ GB RSS). I filed it separately only for (a) the newest build and (b) the thread-writer-locks -> process-set correlation. Happy to close it or move the data into an existing issue if you prefer to keep these consolidated.
Activity
github-actions commentedon Oct 12, 2026
Potential duplicates detected. Please review them and close your issue if it is a duplicate.
Powered by Codex Action
riva03 commentedon Oct 12, 2026
Additional Windows evidence: per-chat helper proliferation remains after sharing a custom MCP
This is additional evidence for the resource-lifecycle discussion in #53285. It does not establish that every helper is orphaned or that an infinite creation loop exists.
Environment: Codex Windows desktop 26.1002.7124.0, bundled native CLI/app-server 0.162.0-alpha.2, Windows x64.
At 2026-10-12 18:53 UTC, a read-only process inventory found 88 node.exe processes across the host. The following 75 were classified by their launcher/script as bundled helpers:
The host total includes other software and is not presented as 88 Codex-owned processes.
A custom MCP was migrated from per-chat stdio to one authenticated, owner-managed loopback Streamable HTTP service. At 18:57 UTC it had 11 MCP sessions, one process and about 27 MiB RSS. A read-only tool call succeeded, and the five previously observed Codex stdio copies of that custom MCP were no longer present. Two stdio copies belonging to a separate Claude configuration remained. No foreign process was terminated. This observation does not establish a causal host RAM saving or identify the desktop transition mechanism.
A CUA in-memory test marker remained readable without reset or navigation. This verifies that marker, not every historical REPL variable or a controlled desktop reload.
The current public config schema describes startup_readiness = "catalog" as allowing a cached tool catalog while the live connection starts. That helps startup readiness; it does not promise first-use process creation. PluginMcpServerConfig also deliberately excludes transport settings.
Suggested owner-run acceptance scenario:
Could the owning component expose first-use startup and bounded reuse/cleanup, or provide a supported state-preserving lifecycle control? The custom shared MCP addresses its own duplication, while the bundled helper counts remain high.
No raw logs, credentials, local paths, chat identifiers, browser URLs or source files are included here.
Ckeplinger199 commentedon Oct 13, 2026
macOS corroboration on the same bundled backend, with an idle-release control tested locally on October 13, 2026.
Codex was becoming sluggish during ordinary use across chats. Restarting cleared the helpers temporarily, but they accumulated again. Users should not need to inspect processes or disable useful plugins to keep the app responsive.
Environment
codex-cli 0.162.0-alpha.17.2.Observed process growth
thread/unsubscribe, after ten minutes of inactivity. The same app-server stayed running. The subsequent sample was 116 descendants / 7.33 GiB summed RSS, and native app control readback still worked.The before/after samples were separated in time during continued use; this is not an exact per-chat memory accounting. Summed RSS includes shared pages. These measurements establish resource reclamation, not a measured UI-latency improvement. Stored system snapshots around the release had no collector warnings.
Lifecycle findings
Read-only inspection of this installed desktop bundle found a three-hour inactive owner retention interval (
108e5milliseconds). Each loaded normal chat eagerly starts its enabled MCP set. This appears to let helper creation outpace reclamation during normal navigation across chats.The installed backend can release an idle, unsubscribed session and shut down its MCP children. Its native unload delay in the matching release is 60 seconds. The local control used a two-second unload grace, so its shutdown timing should not be read as stock behavior. Increasing only the backend timeout would not address a desktop that keeps idle owners subscribed.
There is also a related metadata-generation path: the no-tools ephemeral request disables the
codex_apptool server, but other enabled MCP/plugin servers can still be inherited. A local request filter restricted to that no-tools metadata signature prevented MCP startup in a test with the actual enabled catalog. This is related to #39783; I am not claiming every process in the initial live tree came from metadata generation.Reproduction / controls
thread/unsubscribe. Verify the sentinel exits once the unload grace passes. Resume that same saved thread and verify its MCP tool still works.Seven focused local checks passed, including real backend MCP shutdown and tool operation after saved-thread resume, preservation of model/effort/permissions, activity races, approvals, and ongoing subagent protection. The desktop's six-chat automatic release was verified separately. The workaround wraps the bundled backend; it is not an upstream fix, and no signed application binary or authorization check was modified.
Requested app fix
Please make inactive chat ownership release its MCP resources promptly while retaining resumable chat state, and prevent no-tools metadata generation from starting inherited MCP servers. Add regression tests that exercise repeated chat navigation and metadata generations, verify bounded helper counts after idle, and preserve active work, approvals, subagents and session permissions on resume.
This comment contains a sanitized summary only. No chat transcripts, credentials, customer data, screenshots, configuration dumps, or raw system logs are attached.
Native OpenAI feedback accepted this report with
includeLogs: false, no active thread, and no attachments. Feedback ID:no-active-thread-01a13707-b975-7653-8a9d-fe8f36155cb2.jupiter2021 commentedon Oct 15, 2026
Same here on 26.1007 (Windows 11) — single app-server, tree in the hundreds of processes / 10+ GB, reclamation clearly lagging creation, matching your capture.
While waiting on the upstream fix I built a small Windows stop-gap. The repo README covers the process-level reasoning — the leaked sets are children of the live app-server (not orphans), and codex's runtime exclusive lock on
state_*.sqliteis what makes safe external cleanup hard (no readable per-thread last-activity): https://github.com/jupiter2021/codex-mcp-reaperIt reaps only the continuously-idle MCP sets in the background and leaves the conversation you're actively using alone — stop-gap only, not a substitute for the fix. Happy to share more process-level data if useful.
twk0672005 commentedon Oct 16, 2026
Additional Windows lifecycle controls — 2026-10-16
Adding a bounded follow-up to this existing report rather than opening a duplicate. The repeated resource accumulation remains unresolved; the controls below distinguish retention, reuse, and actual shutdown.
Environment
OpenAI.Codex_26.1007.9446.0.26.1007.71326, build20398. These are different version fields, not an assumed one-to-one mapping.codex-cli 0.162.0-alpha.17.2.Recurrent incidents versus new observations
The owner reported these two incidents and external recovery operations:
The external operator reported checking that the old tree was gone before reopening. These are historical incident reports, not cleanup performed or independently reconstructed by the current diagnostic run. A subsequent startup snapshot had 56 direct children and five Blender/Unity MCP instances each. That snapshot alone does not identify leaks.
1. Actual native subagent / MCP reuse control
One newly created diagnostic subagent completed three ordinary turns. Its Node REPL MCP remained callable. A counter set in turn 2 was incremented from 1 to 2 in turn 3 under the same subagent identity, demonstrating reuse of that REPL state.
After preserving its result, the official archive operation returned success. A subsequent thread read still returned the saved result and reported
notLoaded.Around this operation, the main app-server snapshot changed from 90 direct children / 232 total tree processes to 80 / 205, with another observation at 80 / 205. Process differences were compared using PID, creation time and executable identity. There was concurrent Desktop activity, so I am not assigning every changed PID to that subagent, claiming exact memory savings, or treating this as a complete fix.
2. Controlled installed-backend test: subscription, resume and shutdown
A separate process of the installed backend was launched with a fresh synthetic home, a loopback mock Responses endpoint, and a benign stdio MCP fixture. No production conversations, credentials, real provider requests or application data were used.
The fixture reported its own PID and Windows process creation time. Process liveness was checked against both values, rather than PID alone. Each fixture wrote its own log to avoid concurrent append ambiguity.
In each of three rounds:
mcpServer/tool/call.thread_unload_delay_secs=1: the MCP remains alive.thread/unsubscribe: the owned fixture exits.This exercises the real backend and protocol, but it is not a reproduction of every bundled MCP, the Desktop frontend's subscription ownership, or a long-running production workload. It shows that the normal unsubscribe/archive shutdown path can work in this build; it does not establish that every termination failure path is correct.
Early harness runs encountered an unmaterialized-thread error and a shared-log append race. Those runs are excluded from the above conclusion; only the corrected per-process-record run is used.
3. Version-specific retention observations
The following source files were checked against official commit
740e5af33c71225640e0c1c1555c514c2c93ab74:Read-only inspection of the installed Desktop's packed JavaScript, with the inspected members' declared digests checked, found a 10,800,000 ms (three-hour) inactive-owner retention threshold and an inactive-owner limit of 10. Eligibility also protects active views, followers/shared interests, active/running work and pending work. This is static evidence for that installed policy, not proof that every observed live thread was eligible for release.
An offline experiment using the actual inactive-manager class and controlled clock/state changed only the retention thresholds. Over five batches of six synthetic idle conversations, the original retained counts were
6,10,10,10,10; a two-idle-owner candidate retained2,2,2,2,2. Visible, follower, active, running, pending-tool and unmaterialized-thread controls retained their protection. This candidate was not installed and is not a validated or recommended production workaround. It demonstrates a policy seam; it does not establish full Desktop stability or justify indiscriminate termination.Requested upstream resolution
Could maintainers confirm the intended combined retention budget across Desktop windows/root sessions and V2 worker trees, and provide a supported release policy that:
I would also appreciate clarification of the documentation/default-delay discrepancy. No signed application files or global settings have been modified. No raw logs, configuration dumps, local paths, PIDs, thread identifiers, private content, credentials or bundled binaries are attached.
TyceHerrman commentedon Oct 19, 2026
Related: #22992 is the earlier report of per-thread MCP process accumulation
I think this issue should be consolidated under #22992
jordanlein commentedon Oct 19, 2026
Additional Windows measurements from October 19, 2026, posted with the workstation owner's authorization.
Environment: Windows 11 Pro, build 26300; desktop package OpenAI.Codex 26.1013.4894.0; 31.43 GiB usable RAM. Local MCP integrations included MATLAB, Firebase and other plugin-provided helpers.
Read-only process ancestry tracing found six MATLAB connector parent/child pairs (12 processes) under the same live desktop backend. Pair creation times were 13:55:51, 14:20:02, 14:21:03, 14:21:16, 14:21:17 and 14:21:26 local time. Several batches also contained Firebase/Node and computer-use helpers. Most had little cumulative CPU use. A timestamp comparison with thread-writer lock files did not reliably map these batches to individual chats, so per-chat ownership is not established here.
Snapshots (MiB; summed working sets and private committed bytes are different measurements):
The restarted backend began at 14:27:44. New helper batches appeared around 14:28:00 and 14:28:39, each including one MATLAB connector parent/child pair. The prior helper processes were no longer present after restart.
At the final checkpoint the interface processes accounted for 3394 MiB summed working set, node.exe for 867 MiB, and codex.exe for 474 MiB. The MATLAB application itself was not running in the pre-removal check. The connector was removed from Codex configuration, its executable deleted and its MATLAB add-on uninstalled; zero connector processes remained.
Limitations: these are snapshots during interactive diagnostic/tool use, not an isolated idle experiment. Summed working sets double-count shared pages and are not exclusive physical RAM. The process-tree totals include the diagnostic shell when sampled. Workloads changed across the restart and connector removal, so total deltas cannot be attributed solely to either intervention. Helper counts and total memory decreased during follow-up. We have not demonstrated monotonic growth, a permanent leak, missed cleanup deadlines, or that all retained helpers were unnecessary.
This is evidence of substantial startup/retention overhead and repeated helper batches, rather than a claim of a proven leak. Related: #51649. No raw logs, credentials, private paths or chat contents are attached.