Skip to content

[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

Open

Description

@Edward4226

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:

  1. A cheap, exact way to map each MCP process set to its owning thread, using the per-thread lock files.
  2. 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-server process; 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 npx and uvx based 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-git 13, server-memory 53, node_repl 39, cua-repl 26, server.mjs 42.

After roughly 9 hours without touching those chats, the same app-server had released part of it:

  • mcp-server-git 13 -> 5, node_repl 39 -> 15, server-memory 53 -> 21, cua-repl 26 -> 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

  1. added
    bugSomething isn't working
    appIssues related to the Codex desktop app
    windows-osIssues related to Codex on Windows systems
    mcpIssues related to the use of model context protocol (MCP) servers
    app-serverIssues involving app server protocol or interfaces
    on Oct 12, 2026
  2. github-actions commented on Oct 12, 2026

    @github-actions
    Contributor
  3. riva03 commented on Oct 12, 2026

    @riva03

    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:

    Component Processes
    Codex App Tools 20
    Code Review 19
    CUA 20
    Node REPL 16

    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:

    1. Load two disposable chats with benign MCP fixtures, without invoking their tools. Record process identity, birth time, ownership and effective configuration. Verify first-use startup through a supported discovery/catalog mechanism.
    2. Invoke a tool, then perform twenty reconnect cycles. Compatible stateless services should reuse a bounded instance; project, account and credential boundaries must remain separate. Stateful browser/REPL context must remain scoped to its chat.
    3. Record removal of the last subscriber and completion/cancellation of pending work. After confirmed thread unload, verify owned temporary helpers exit. An idle UI label, zero CPU or silence alone is insufficient evidence of abandonment.
    4. Preserve useful browser/REPL state while its owner still needs it, and measure memory commitment plus comparable before/during/after resource samples.

    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.

  4. Ckeplinger199 commented on Oct 13, 2026

    @Ckeplinger199

    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

    • Unified ChatGPT/Codex desktop 26.1007.30912, build 20156.
    • Bundled codex-cli 0.162.0-alpha.17.2.
    • Apple Silicon Mac Studio, 96 GB RAM, Darwin 25.6.0.
    • Multiple enabled stdio MCP/plugin servers. Memory pressure, disk space and thermals were healthy; no swapping was observed at the initial capture.

    Observed process growth

    • Initial live app-server: 321 direct children, approximately 367 descendants and 25.22 GiB summed RSS.
    • After an authorized relaunch and continued use: 257 descendants / 15.49 GiB summed RSS.
    • A local policy then unsubscribed six saved idle chats through 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 (108e5 milliseconds). 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_app tool 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

    1. Enable several healthy stdio MCP/plugin servers and launch the desktop normally.
    2. Open or resume several saved chats, finish their work, and navigate away while leaving the app running.
    3. Record the app-server PID and its descendant process tree/RSS over time. Correlate the new cohorts with chat loads; distinguish working chats and subagents from saved idle chats.
    4. In a controlled app-server client, start a saved session with a sentinel stdio MCP server, wait for idle, then send thread/unsubscribe. Verify the sentinel exits once the unload grace passes. Resume that same saved thread and verify its MCP tool still works.
    5. Compare repeated no-tools ephemeral metadata starts with an explicit configuration that disables all inherited MCP/plugin tool startup for those requests.

    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.

  5. jupiter2021 commented on Oct 15, 2026

    @jupiter2021

    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_*.sqlite is what makes safe external cleanup hard (no readable per-thread last-activity): https://github.com/jupiter2021/codex-mcp-reaper

    It 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.

  6. twk0672005 commented on Oct 16, 2026

    @twk0672005

    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

    • Windows Store package: OpenAI.Codex_26.1007.9446.0.
    • Desktop's information-only update tool reports app version 26.1007.71326, build 20398. These are different version fields, not an assumed one-to-one mapping.
    • Executed backend: codex-cli 0.162.0-alpha.17.2.
    • Multiple enabled stdio MCP integrations, including Blender, Unity and the bundled Node REPL.

    Recurrent incidents versus new observations

    The owner reported these two incidents and external recovery operations:

    Date Direct children of the main app-server before intervention Processes removed in the externally cleaned old tree
    October 15 255, including 23 Blender MCP and 24 Unity MCP 695
    October 16 257 after the prior restart 679

    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:

    1. Start a thread and call the fixture's identity tool through mcpServer/tool/call.
    2. Complete a mock turn so there is actual persisted history.
    3. Resume the already-loaded thread: the same MCP process is reused.
    4. Remain idle while still subscribed, longer than a test-only thread_unload_delay_secs=1: the MCP remains alive.
    5. Call thread/unsubscribe: the owned fixture exits.
    6. Resume the saved thread: its synthetic result is still present and the MCP tool works again.
    7. Archive, unarchive/resume, call the tool again, and archive: the owned fixture count returns to zero.
    8. After all rounds, close the owned backend normally: exit code 0, zero surviving fixture identities.

    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 retained 2,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:

    • Bounds unused idle ownership and unnecessary MCP startup while preserving normal subagent collaboration.
    • Preserves active work, approvals, shared/stateful tool contexts and resumable results.
    • Exposes enough lifecycle/ownership information to distinguish retained resources from failed teardown.
    • Tests repeated chat restore/navigation, multiple worker trees, tool reuse, and observed process exit; archive acknowledgement or a lower process count alone should not be the acceptance criterion.

    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.

  7. TyceHerrman commented on Oct 19, 2026

    @TyceHerrman

    Related: #22992 is the earlier report of per-thread MCP process accumulation

    I think this issue should be consolidated under #22992

  8. jordanlein commented on Oct 19, 2026

    @jordanlein

    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):

    Checkpoint Entire desktop process-tree working set Tree private bytes node.exe count MATLAB MCP process count
    Before restart, 14:24 6692 6345 24 12
    After user terminated/reopened app, 14:28 5591 5502 19 4
    After removing MATLAB MCP, 14:33 5145 4838 10 0

    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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    appIssues related to the Codex desktop appapp-serverIssues involving app server protocol or interfacesbugSomething isn't workingmcpIssues related to the use of model context protocol (MCP) serversperformancewindows-osIssues related to Codex on Windows systems

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions