Skip to content

VS Code extension leaks MCP server processes — one full set per session, never reaped (up to 490 processes / 26.5 GB observed) #38537

Open

Description

@dvass88

What version of the IDE extension are you using?

openai.chatgpt 26.803.41515 and 26.803.61601 (macOS x86_64 binary) — reproduced on both

What subscription do you have?

pro 20x

Which IDE are you using?

VS Code

What platform is your computer?

macOS (Darwin 25.5.0), x86_64

What issue are you seeing?

Each Codex session spawns a complete set of the configured MCP servers as children of the extension's codex app-server process. When a session ends (or a new one starts), the old set is never terminated. Sets accumulate for as long as the extension host lives — days — at roughly 80–160 MB RSS per process.

Setup: ~8 MCP servers configured (argent, @playwright/mcp, xcodebuildmcp, @upstash/context7-mcp, @lottiefiles/creator-mcp, plus 3 project-local stdio servers).

Measured occurrences (same machine, Aug 6–14, 2026)

  • Aug 6: 14 leaked sets, 110 processes, ~8 GB resident; swap 8.2/9.2 GB
  • Aug 9: ~20 sets, 243 processes, oldest 25+ h; swap 7.45/8.19 GB
  • Aug 12 (worst): 71 sets, 490 processes, 26.5 GB resident; swap 25.4/26.6 GB — only 1.2 GB of swap left
  • Aug 14 (twice): 19 sets / 169 processes, then 9 sets / 108 processes (~8 GB) after ~1 day of uptime — this time with swap healthy, so the leak shows up as general system jank well before memory pressure

Failure mode under pressure (Aug 12)

When memory is exhausted, MCP server startup fails, and the extension responds by respawning the entire set — we measured ~1.5 new sets per minute against only 9 actual session rollouts in ~/.codex/sessions in the same hour. That feedback loop is what users experience as "Codex could not start": the session journal shows the agent completing turns normally while the UI can't attach to MCP. The respawn storm stopped on its own the moment the leaked processes were killed.

What steps can reproduce the bug?

  1. Configure a set of MCP servers for the extension (~8 in our case: argent, @playwright/mcp, xcodebuildmcp, @upstash/context7-mcp, @lottiefiles/creator-mcp, plus 3 project-local stdio servers).
  2. Open VS Code with the extension and start a Codex session. A full set of MCP server processes is spawned as children of the extension's codex app-server process.
  3. End that session and/or start a new one. A second full set is spawned; the first set is still running.
  4. Repeat over normal daily use and leave the extension host running for hours/days.
  5. Inspect the process tree (e.g. ps -eo pid,ppid,rss,etime,command | grep -i mcp, or pstree from the codex app-server / codex PID) and count the sets.

Observed: one full set of MCP server processes per session, none reaped. On this machine that reached 71 sets / 490 processes / 26.5 GB resident after several days of uptime (see the measurements above). Under memory pressure the extension additionally respawns sets at ~1.5/minute, far in excess of the actual session count in ~/.codex/sessions.

What is the expected behavior?

Old MCP server sets are terminated when their session ends, or at minimum reaped when a new set is spawned / on extension host shutdown.

Additional information

  • Every leaked process (7 occasions, ~1,300 processes total) exited cleanly on plain SIGTERM; none required SIGKILL and none orphaned to init. So this appears to be purely missing lifecycle cleanup, not hung children.
  • After an extension auto-update, the superseded version's extension host can linger with all its leaked children; nothing ever cleans those up either (observed Aug 12: old host holding 26 GB while the updated build failed to start).
  • The parent process shape changes between builds (sometimes codex app-server owns the sets, sometimes the main codex process does), but the leak reproduces in both.

Activity

  1. added
    bugSomething isn't working
    mcpIssues related to the use of model context protocol (MCP) servers
    app-serverIssues involving app server protocol or interfaces
    on Aug 14, 2026
  2. xtyooo commented on Sep 4, 2026

    @xtyooo

    Windows Codex Desktop reproduction: per-session stdio MCP bundles still accumulate after the lifecycle fix in #19753

    I can reproduce the same process-tree shape on Windows Codex Desktop. This expands the report beyond the macOS VS Code extension case and may help distinguish an app/extension session-ownership gap from the lower-level McpConnectionManager shutdown bug fixed by #19753.

    Environment

    • Windows 10 Pro x64, version 10.0.19045
    • Codex Desktop build: 26.707.31428
    • Bundled backend: codex-cli 0.144.0-alpha.4
    • Long-lived parent: one codex.exe ... app-server process
    • MCP transport: stdio
    • Packaging caveat: this is a repacked Windows distribution. I compared the extracted .vite/build payload against its preserved original ASAR backup and the core build files were byte-identical. The bundled codex.exe is the binary reporting the version above.

    Observed failure

    Before cleanup, one app-server process owned 414 descendants:

    Process Count
    node.exe 160
    cmd.exe 140
    python.exe 40
    uv.exe 20
    uvx.exe 20
    node_repl.exe 10
    mcp-server-fetch.exe 10
    mcp-sequential-thinking.exe 10
    • App-server tree private memory: 15,542 MB
    • App-server tree working set: 10,122 MB
    • System committed memory: 32.88 / 33.67 GB (97%)
    • The repeated sets were created between 21:39 and 23:32 local time.
    • Killing/restarting the single Codex Desktop/app-server tree removed the accumulated children.

    The exact multiples are consistent with ten complete MCP startup cycles, not ten Codex app-server processes.

    Clean-start timeline

    After a full process-tree cleanup and restart:

    2026-09-04T15:53:21.036Z  AppServer response: method=thread/start
    2026-09-04T15:53:21.xxxZ  first complete stdio MCP bundle starts
    
    2026-09-04T15:55:01.718Z  maybe_resume_started for an existing thread
    2026-09-04T15:55:02.376Z  AppServer response: method=thread/resume
    2026-09-04T15:55:02.xxxZ  second complete stdio MCP bundle starts
    

    There was no session/MCP shutdown event between thread/start and thread/resume. Both bundles remained direct descendants of the same app-server process.

    This is especially easy to trigger in Desktop because startup opens/prewarms a new-thread route, then navigating to an existing conversation resumes a second session.

    Why this appears to survive #19753

    The local backend appears to contain the #19753 lifecycle code: its binary contains the added strings MCP client is shut down, failed to initialize MCP client during shutdown, and Shutting down Codex instance.

    PR #19753 added explicit MCP teardown for session shutdown, submission-channel closure, manager refresh/replacement, and temporary connector probing. Based on the absence of a shutdown event in this reproduction, the first session appears to remain owned/alive when the UI resumes another thread, so its manager is neither replaced nor shut down. The lower-level termination code cannot run if the app-server session never reaches a shutdown path.

    This suggests a remaining lifecycle gap above RmcpClient::shutdown() rather than the original absence of an MCP process terminator.

    Mitigation experiment

    I reduced the enabled MCP configuration to four actually used servers (fetch, github, playwright, and the Desktop-managed node_repl) and restarted cleanly:

    • Initial steady state: 20 descendants, 709 MB private memory
    • After further normal session activity: three repeated bundles, 50 descendants, 1,804 MB private memory
    • Direct bundle start times: 00:14:12, 00:26:46, and 00:26:49 local time
    • Disabled MCP servers did not reappear

    This bounds the impact but confirms the per-session accumulation still occurs and scales linearly with the number of retained bundles.

    Suggested regression coverage / investigation

    An app-server integration test could use a stub stdio MCP server that writes its PID, then reproduce the Desktop ownership sequence:

    1. thread/start session A and wait for MCP A's PID.
    2. Resume session B through the same app-server client/owner flow.
    3. Exercise the same detach/navigation/owner transition used by Desktop.
    4. Assert MCP A exits after session A has no active turn and no owners/subscribers.
    5. Assert MCP B remains alive, so legitimate parallel threads are not reaped.

    The key question appears to be which layer should declare session A unreachable: the Desktop/extension client, app-server owner/subscription tracking, or a bounded idle-session eviction policy. Simply shutting down the previous session on every thread/resume would break legitimate parallel work, so cleanup likely needs to follow ownership plus active-turn state.

    I can provide additional sanitized process snapshots or run a targeted test build if maintainers identify the intended session-detach path.

  3. vangj commented on Sep 20, 2026

    @vangj

    Linux reproduction on Codex CLI 0.154.0 with a project-local stdio Graphify MCP server:

    • 47 retained Python MCP server processes under the long-lived Codex app-server process
    • 6.51 GiB aggregate RSS
    • 5 loaded server processes at roughly 0.9-1.0 GiB RSS each
    • 66 abandoned graph-refresh staging directories consuming about 34 GiB
    • the graph artifact is 270,344,485 bytes
    • a direct fresh CLI query succeeds, taking 20.4 seconds and peaking at 956,328 KiB RSS
    • in the affected session, the project MCP registration is enabled, but the Graphify MCP tools are unavailable

    This resembles the app-server ownership gap described above rather than the lower-level shutdown defect fixed by #19753: old per-session server trees remain live, while a direct non-MCP invocation still works. The large graph amplifies the memory impact of every retained MCP process, but it does not explain why obsolete per-session processes remain owned.

    The corresponding downstream investigation was rocketvector/darkstar-bbn#940. We are closing it as an upstream lifecycle issue rather than convening a repository architecture council. No ambiguous processes were terminated during diagnosis.

  4. chrisl10 commented on Oct 15, 2026

    @chrisl10

    Adding a data point that extends this beyond the VS Code extension: the same per-session spawn-without-reap happens under any long-lived codex app-server process driven programmatically (orchestrator creating threads over the MCP interface), codex-cli 0.162.1, macOS.

    Observed on one engine over a few hours of multi-thread work: 1 MCP child at engine start, +2 when a second thread came up, +11 spawned within 2 seconds during a thread-creation burst, total 14 live children (~65 MB RSS each, ~0.9 GB) with zero reaped, including the children from the first hour, after threads finished their work.

    Two extra signals that may help triage:

    1. The 11-at-once fork burst coincided with the orchestrator's engine health poll timing out ~45 seconds later, so the spawn storms are externally visible as engine stalls, not just a slow leak.
    2. Reproduction is simple without the extension: start codex app-server, create several threads programmatically, then pgrep -f <your-mcp-server> | wc -l climbs per thread and never decreases, even after those threads complete.

    This suggests the leak lives in the app-server/engine MCP lifecycle itself rather than the extension layer.

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

    app-serverIssues involving app server protocol or interfacesbugSomething isn't workingextensionIssues related to the VS Code extensionmcpIssues related to the use of model context protocol (MCP) serversperformance

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions