Skip to content

Regression: Playwright MCP stdio processes still leak after #16895 fix — 213 orphaned pairs, 13.6 GB RSS #17832

Open

Description

@RedesignedRobot

Regression: Playwright MCP stdio processes still leak after #16895 fix — 213 orphaned pairs, 13.6 GB RSS

Environment

Field Value
Codex CLI codex-cli 0.120.0
Codex App 26.409.20454
Subscription ChatGPT Pro
Platform macOS 26.4.1 (25E253), Apple M3 Pro, 36 GB RAM
MCP server @playwright/mcp@latest (stdio transport)

Summary

Issue #16895 (stdio MCP child processes not cleaned up) was closed on April 9 as fixed. As of April 14 on codex-cli 0.120.0, the bug is still present — specifically for @playwright/mcp configured as a global stdio MCP server in config.toml.

After a normal day of use in Codex Desktop involving multi-agent / subagent workflows, 213 leaked npm exec @playwright/mcp@latest + node playwright-mcp process pairs accumulated under the codex app-server process tree, consuming 13.6 GB of RSS and contributing to Activity Monitor reporting ~24 GB total for Codex.

This is the same root cause as #17574 (xcodebuild/chrome-devtools MCP leak) and #12491 (original 37 GB / 1300+ zombie report). The lifecycle fix from #16895 did not fully resolve the problem — subagent thread teardown still does not reap MCP child processes.

Forensic data

Process tree before cleanup

codex app-server (PID 4938, 993.8 MB physical footprint)
├── 213 × npm exec @playwright/mcp@latest  (~35 MB each = 7.4 GB)
│   └── each spawns: node playwright-mcp   (~30 MB each = 6.2 GB)
└── (total leaked MCP processes: 426, total leaked RSS: 13.6 GB)

Memory breakdown (physical footprint via vmmap)

Process PIDs Physical Footprint
Codex main (Electron) 4892 177.0 MB
codex app-server (Rust) 4938 993.8 MB
Renderer #1 4950 560.2 MB
Renderer #2 5676 134.5 MB
Renderer #3 5729 134.7 MB
GPU process 4895 514.6 MB
Network service 4897 16.3 MB
Tracing service 61658 51.3 MB
213 × npm exec playwright various 7,550 MB (7.4 GB)
213 × node playwright-mcp various 6,304 MB (6.2 GB)
Grand total 433 processes ~15.3 GB RSS

app-server malloc analysis (PID 4938)

DefaultMallocZone: 179.3 MB resident + 792.5 MB swapped
                   5,023,995 allocations, 880.6 MB allocated
                   335.8 MB stack across 61 threads

The app-server itself has ~5M live allocations and 792 MB swapped out — likely holding session state / MCP connection metadata for all 213 leaked connections.

Reproduction context

  • config.toml has @playwright/mcp@latest as a global stdio MCP server
  • Normal usage pattern: Codex Desktop with multi-agent workflows (max_threads = 20, max_depth = 2)
  • 927 total threads in state_5.sqlite, 476 spawn edges
  • Heavy subagent use: one parent thread spawned 58 children, another 37
  • Each subagent session appears to spawn → session_init → mcp_manager_init → spawn new MCP processes
  • When the subagent session closes, the MCP processes are not terminated

Log evidence from logs_2.sqlite

The codex_core::spawn and codex_mcp::mcp_connection_manager modules show the pattern:

  1. thread_spawn creates a new subagent session
  2. session_init → mcp_manager_init starts fresh MCP connections (including Playwright)
  3. Session completes and thread is marked closed in thread_spawn_edges (status = "open" — note: even completed edges show status "open", suggesting the edge status itself may not be updated)
  4. The spawned npm exec @playwright/mcp@latest and its node playwright-mcp child remain alive indefinitely

Immediate workaround

pkill -f "playwright-mcp"
pkill -f "npm exec @playwright/mcp"

This instantly freed ~13.6 GB. Playwright MCP respawns correctly for the next thread that needs it.

Expected behavior

  • MCP child processes should be terminated when their owning thread/session closes
  • Subagent sessions should either share the parent's MCP connections or reliably clean up their own
  • The codex app-server should not accumulate unbounded child processes over the course of normal use

Related issues

Activity

  1. added
    bugSomething isn't working
    appIssues related to the Codex desktop app
    mcpIssues related to the use of model context protocol (MCP) servers
    app-serverIssues involving app server protocol or interfaces
    subagentIssues involving subagents or multi-agent features
    on Apr 14, 2026
  2. removed
    appIssues related to the Codex desktop app
    subagentIssues involving subagents or multi-agent features
    app-serverIssues involving app server protocol or interfaces
    on Apr 14, 2026
  3. RedesignedRobot commented on Apr 14, 2026

    @RedesignedRobot
    Author

    Root Cause Analysis

    Verified across 4 independent passes of the codex-rs/ source on main.

    Update (2026-04-19): fix landed on branch RedesignedRobot:codex:fix/mcp-subprocess-cleanup, rebased onto current main, cargo check -p codex-core -p codex-mcp clean. Shipped impl is a sync self.clients.clear() leaning on the Drop chain (equivalent effect, simpler than the async draft below). Also extends the same teardown to the submission_loop channel-close fallthrough, which previously drained only guardian state. The paths referenced below predate the codex-rs/core/src/codex.rs split: current locations are codex-rs/core/src/session/handlers.rs (shutdown at L910, submission_loop fallthrough at L1200) and codex-rs/codex-mcp/src/mcp_connection_manager.rs (impl block).

    Summary

    McpConnectionManager has no cleanup mechanism. handlers::shutdown() terminates shell processes but never touches MCP. Child processes accumulate because nothing initiates the drop chain to ProcessGroupGuard.

    The drop chain that should fire but does not

    handlers::shutdown()
      -> unified_exec_manager.terminate_all_processes()   // shell cleanup - present
      -> mcp_connection_manager.shutdown()                // MCP cleanup - missing (method does not exist)
        -> drop AsyncManagedClient
          -> drop ManagedClient (Arc<RmcpClient>)
            -> drop ClientState::Ready._process_group_guard
              -> ProcessGroupGuard::drop()
                -> terminate_process_group(pgid)          // SIGTERM, then SIGKILL after 2s
    

    ProcessGroupGuard::drop() at rmcp_client.rs:385 works correctly. It is never invoked.

    Verified gaps

    1. handlers::shutdown() ignores MCP — codex.rs L5814

    Calls unified_exec_manager.terminate_all_processes() for shell processes. Zero references to mcp_connection_manager.

    2. McpConnectionManager has no cleanup code — mcp_connection_manager.rs, 1824 lines

    No Drop impl. No shutdown(). No close(). No terminate(). The clients: HashMap<String, AsyncManagedClient> holding Arc<RmcpClient> references is never cleared.

    3. shutdown_live_agent() does not force MCP teardown — agent/control.rs

    Sends Op::Shutdown, calls remove_thread() (decrements Arc refcount), calls release_spawned_thread(). Does not touch MCP. If any reference to the Session survives, the drop chain stalls.

    4. CancellationToken is startup-only — mcp_connection_manager.rs

    Wraps start_server_task().or_cancel(&cancel_token). Only cancels in-progress connection attempts. Inert once a client reaches ClientState::Ready.

    5. Each subagent creates its own MCP connections — codex.rs, Session::new()

    Each Session::new() calls McpConnectionManager::new(), spawning fresh child processes per subagent. Per-session scoping is correct. Per-session cleanup does not exist.

    PR #17223 was incomplete

    Changed 3 files in codex-rs/app-server/. Fixed connection_closed() in codex_message_processor.rs — handles WebSocket client disconnect. Does not address subagent internal teardown. Subagents do not go through the WebSocket disconnect path.

    Log evidence

    From logs_2.sqlite on this machine:

    • MCP init events (current process): 3
    • MCP cleanup events (current process): 0
    • Subagent threads spawned: 521
    • thread_spawn_edges still status "open": 315
    • rmcp "RunningService dropped without explicit close()" warnings: 4 (only in dead processes)

    Fix (as shipped on branch)

    // 1. McpConnectionManager::shutdown — sync drain; Drop chain handles the rest
    impl McpConnectionManager {
        pub fn shutdown(&mut self) {
            self.clients.clear();
        }
    }
    
    // 2. handlers::shutdown() — add after unified_exec_manager cleanup
    sess.services.mcp_connection_manager.write().await.shutdown();
    
    // 3. submission_loop channel-close fallthrough — mirror the same teardown
    sess.services.unified_exec_manager.terminate_all_processes().await;
    sess.services.mcp_connection_manager.write().await.shutdown();

    The original draft used an async shutdown().await with explicit managed.client.close().await per client. The sync clients.clear() variant is equivalent: dropping AsyncManagedClient releases the Arc<RmcpClient>, which drops ProcessGroupGuard and triggers terminate_process_group. Fewer moving parts, no await needed.

    Files

    File Issue
    codex-rs/core/src/codex.rs L5814 handlers::shutdown() missing MCP cleanup
    codex-rs/codex-mcp/src/mcp_connection_manager.rs No shutdown/Drop/close
    codex-rs/core/src/agent/control.rs shutdown_live_agent() does not trigger MCP teardown
    codex-rs/rmcp-client/src/rmcp_client.rs L385 ProcessGroupGuard::drop() works but is never reached
  4. RedesignedRobot commented on Apr 14, 2026

    @RedesignedRobot
    Author

    Reference implementation: main...RedesignedRobot:codex:fix/mcp-subprocess-cleanup

    2 files, +23/-2. Covers both handlers::shutdown() (Op::Shutdown path) and the submission_loop channel-close fallthrough. cargo check -p codex-core -p codex-mcp clean.

    Verified across 7 independent code review passes covering Drop semantics, call ordering, concurrency safety, and termination path coverage. Covers all primary termination paths. No deadlock risk in the Drop chain.

  5. fortunexbt commented on Apr 15, 2026

    @fortunexbt

    @RedesignedRobot this is the worst MCP lifecycle failure I have seen documented — 426 orphaned process pairs from 927 threads because subagent teardown never reaps MCP children. The forensic detail is excellent.

    The operators I talk to hit this most often in unattended multi-agent runs where the process leak accumulates over hours until the host OOMs. The root cause you traced — no McpConnectionManager.shutdown() in handlers::shutdown() — explains why PR #17223 only fixed the WebSocket disconnect path while subagents kept leaking.

    Is the bigger gap for you right now per-agent process isolation so leaks stay bounded, or runtime visibility that surfaces which subagents leaked their MCP processes before the host runs out of memory?

  6. RedesignedRobot commented on Apr 15, 2026

    @RedesignedRobot
    Author

    The immediate gap is the missing teardown call. handlers::shutdown() never invokes cleanup on McpConnectionManager. The reference implementation linked above addresses this directly. Once the drop chain fires correctly, the leak stops at source.

    Process isolation is a secondary concern. Each subagent already gets its own McpConnectionManager via Session::new(), so the scoping is correct. The problem is purely that the cleanup path was never wired. Bounding leaks per-agent would be defense-in-depth, not a fix for the root cause.

    Runtime observability would be the more useful follow-up. A process count metric emitted per-session at teardown (or periodically by the app-server) would catch regressions and make leaks visible before they reach OOM. The structured logging infrastructure is already there (logs_2.sqlite, codex_mcp::mcp_connection_manager target). It just needs a counter for active stdio child processes.

    For unattended multi-agent runs specifically, a lightweight reaper in the app-server that periodically reconciles live MCP child PIDs against active thread_spawn_edges would catch any edge cases the explicit teardown misses (forced SIGTERM, caller timeouts, Arc reference leaks).

  7. 2 remaining items

  8. RedesignedRobot commented on Apr 19, 2026

    @RedesignedRobot
    Author

    Hey @jif-oai @etraut-openai, dropping a note in case it's useful.

    Patch is ready on RedesignedRobot:codex:fix/mcp-subprocess-cleanup, rebased on main.

    Fix: adds McpConnectionManager::shutdown() (drains the client map) and calls it from handlers::shutdown() alongside the existing shell teardown. Dropping the Arc<RmcpClient> refs lets ProcessGroupGuard::drop() fire and SIGTERM the MCP child process groups. This is the subagent shutdown path #17223 didn't cover.

    Also covers the submission_loop channel-close fallthrough, which previously drained only guardian state and leaked the same processes on abnormal channel closure.

    2 files, +23/-2, no behavior change on the happy path. cargo check -p codex-core -p codex-mcp clean.

    Happy to open as a PR if you can invite me in.

    Thanks.

  9. added a commit that references this issue on Apr 19, 2026
    498aac6
  10. gregoramon commented on Apr 28, 2026

    @gregoramon

    Additional data point: Codex.app desktop, version 0.125.0-alpha.3

    Confirms the regression persists in the current alpha.

    Environment

    Field Value
    Codex codex-cli 0.125.0-alpha.3 (Codex.app desktop)
    Platform macOS 26.3.1, Apple Silicon
    Codex.app uptime ~33 hours (single launch)
    MCP server @playwright/mcp@latest

    Observed pattern

    Six orphaned npm exec @playwright/mcp@latest processes, all with PPID = Codex app-server (PID 46443). They spawn in pairs ~1–2 minutes apart, on three distinct occasions over the 33h Codex.app uptime:

    PIDs Pair started Age
    47054, 48285 Sun Apr 26 10:01:49 / 10:03:48 31h
    21593, 22586 Sun Apr 26 20:20:47 / 20:22:18 21h
    83017, 84080 Mon Apr 27 16:05:23 / 16:07:09 ~1h (active)

    Matches the root cause @RedesignedRobot traced: McpConnectionManager was replaced (3× here, presumably on session/reconnect) without draining its clients map, so the Arc<RmcpClient> refs that should trigger ProcessGroupGuard::drop() are never released. Codex.app itself never exited, so no shutdown path fires.

    46443  Codex app-server (alive 33h)
    ├── 47054 @playwright/mcp    ← stale, 31h
    ├── 48285 @playwright/mcp    ← stale, 31h
    ├── 21593 @playwright/mcp    ← stale, 21h
    ├── 22586 @playwright/mcp    ← stale, 21h
    ├── 83017 @playwright/mcp    (current session)
    └── 84080 @playwright/mcp    (current session)
    

    The 31h-old pair had spawned `chrome-headless-shell` children running 1d 3h, two of them holding 13.3% / 8.3% CPU steady.

    Resource impact

    On a 64 GB machine, after 33h of Codex.app uptime:

    • Compressor: 20 GB physical / ~47 GB logical
    • Swap: 2.1 GB used (3 GB allocated)
    • Memory pressure high enough to be visible in interactive workloads (Zoom, Chrome)

    `kill ` on the stale orchestrators terminated cleanly — they were orphaned, not stuck.

  11. etraut-openai commented on Apr 28, 2026

    @etraut-openai
    Contributor

    I have a pending PR that should address this. If you're so inclined, you could try building the PR from source and confirm that it does address the problem you're seeing.

  12. nazordz commented on May 7, 2026

    @nazordz

    Additional data point: still reproducible with @mui/mcp on macOS after the MCP shutdown fix.

    Environment

    • Codex: 0.128.0
    • Platform: macOS, Apple Silicon
    • MCP server: @mui/mcp@latest configured globally via stdio/npx

    Config:

    [mcp_servers.mui-mcp]
    command = "npx"
    args = ["-y", "@mui/mcp@latest"]

    Observed

    After repeated Codex sessions, I found:

    • 59 npm exec @mui/mcp@latest parent processes
    • 59 matching node .../.npm/_npx/.../node_modules/.bin/mcp child processes
    • 58 of the npm exec @mui/mcp@latest parents had PPID 1, adopted by launchd
    • Total MUI MCP-related RSS was about 767 MB
    • CPU was mostly idle, but processes accumulated across sessions

    Example stale process group:

    PID 1428  PPID 1     PGID 1428  npm exec @mui/mcp@latest
    PID 1994  PPID 1428  PGID 1428  node /Users/.../.npm/_npx/.../node_modules/.bin/mcp
    

    Current live Codex session creates a fresh group:

    PID 8934  PPID 51604  PGID 8934  codex
    PID 8973  PPID 8934   PGID 8973  npm exec @mui/mcp@latest
    PID 9518  PPID 8973   PGID 8973  node /Users/.../.npm/_npx/.../node_modules/.bin/mcp
    

    Permission check

    The stale processes are owned by my user. A harmless signal permission check succeeds outside Codex's restricted context:

    kill -0 1428
    kill -0 -1428

    Both return success.

    Codex log evidence

    Codex logs contain repeated MCP cleanup failures:

    WARN codex_rmcp_client::stdio_server_launcher: Failed to terminate MCP process group ... Operation not permitted (os error 1)
    WARN codex_rmcp_client::rmcp_client: Failed to terminate MCP process group ... Operation not permitted (os error 1)
    

    Interpretation

    This looks like the stdio MCP cleanup leak still reproducing with @mui/mcp@latest on Codex 0.128.0. The processes are same-user and signalable from the host, but Codex cleanup appears to fail from its sandbox/restricted context, leaving MCP process groups orphaned under launchd.

  13. mySebbe commented on May 12, 2026

    @mySebbe

    Additional Windows process-growth data point from Codex Desktop 26.506.3741.0.

    This is not @playwright/mcp specifically, but it looks like the same class of browser automation lifecycle problem: long-lived Chrome/Playwright controlled browser profiles are not returning to a bounded baseline.

    Observed live process footprint:

    controlled_chrome_profile / remote-debugging-port=9444:
      chrome.exe processes: 74
      working set: ~10.4 GB
      oldest process: 2026-05-12 02:19:30
      newest process: 2026-05-12 13:27:48
    
    playwright_chromiumdev_profile temp profile:
      chrome.exe processes: 20
      working set: ~1.6 GB
      oldest process: 2026-05-12 01:42:55
      newest process: 2026-05-12 02:11:16
    
    CodexFabChromeProfile / remote-debugging-port=9333:
      chrome.exe processes: 7
      working set: ~0.47 GB
    

    The largest group is the fallback/controlled Chrome profile, not the node_repl.exe MCP hosts. A local conservative cleanup is now managing stale node_repl.exe, but browser fallback Chrome processes remain the dominant footprint.

    Cross-links where the routing/fallback side is discussed: #21868, #19365, #20417.

  14. waypointmini-rgb commented on May 31, 2026

    @waypointmini-rgb

    Independent reproduction — same lifecycle bug still present on a much later Codex App build, with a different MCP server set, so this is not specific to @playwright/mcp or to the chrome-devtools-mcp / xcodebuildmcp pair in #17574.

    Environment

    Field Value
    Codex App 26.527.30818 (build 3370)
    Codex CLI codex-cli 0.135.0-alpha.1
    Bundle id com.openai.codex
    Subscription ChatGPT Pro
    Platform macOS 26.5
    MCP servers configured obsidian-mcp, notebooklm-mcp, @playwright/mcp@latest (stdio), SkyComputerUseClient mcp

    Forensic data — single live Codex App session, ~19h uptime

    Single live codex app-server --analytics-default-enabled parent (PID 49055, age 19h14m) owns the entire leaked tree. Parent is alive and healthy; children are not orphans.

    codex app-server --analytics-default-enabled (PID 49055, alive 19h14m)
    ├── 30 × obsidian-mcp
    ├── 24 × SkyComputerUseClient mcp
    ├── 19 × notebooklm-mcp
    └── 13 × node npm exec @playwright/mcp@latest
       (54 MCP children rooted directly on the live app-server PID)
    

    This is the exact pattern from #17574 — every subagent / computer-use session refresh runs session_init.mcp_manager_init, spawns a fresh MCP quartet, and the previous quartet is never reaped even though the new session is now wired to its own helpers. Ratio is roughly 1 quartet per session refresh; with computer-use refreshing about hourly, that's ~20 quartets accumulated in this window. Matches the per-session-init growth rate in #17574 exactly.

    Why this is not the SDK-side stdin-EOF leak

    A common confusion: the typescript-sdk side has a related leak class fixed by modelcontextprotocol/typescript-sdk#2003 — StdioServerTransport not exiting on stdin close. That fix only helps when the MCP server's parent has actually died and stdin EOF has been delivered.

    Here the Codex App app-server parent is still alive and (I assume) still holding the stdio pipes open for each abandoned child. From the child's perspective stdin is not closed, so no SDK-side stdin-EOF / close listener can save us. The teardown receipt has to come from the app-server side — either explicit kill of the prior session's child set on session-init, or closing the pipes so the SDK-side EOF handler can take over.

    Suggested triage

    Happy to share further lsof / ps -axo pid,ppid,etime,command captures or a longer time series if it would help reproduce on the maintainer side.

  15. nukk-pain commented on Jul 14, 2026

    @nukk-pain

    Adding a current non-Playwright reproduction of the same subagent stdio MCP teardown failure.

    Environment

    • Codex CLI: 0.144.3
    • Surface: CLI, multi-agent v2
    • Platform: macOS 26.5.1, Darwin arm64
    • MCPs: Codegraph plus OMO-provided stdio MCP servers
    • Per-process soft file descriptor limit: 256

    Observed

    Across local session JSONL logs, I found 54 distinct tool-call failures with:

    Failed to create unified exec process: Too many open files (os error 24)
    

    The failures affected 11 session logs over about 67 minutes. Once degraded, even pwd and git status --short could not start.

    For one long-running root Codex process:

    • 17 spawn_agent calls in the root session log
    • 34 direct child processes still attached to the root
    • 16 Codegraph launcher children
    • 16 additional OMO MCP launcher children
    • 173 open file descriptors, including 99 pipes
    • the latest agent snapshot showed only 2 running and 9 completed agents

    The direct child count tracks the accumulated subagent MCP stacks rather than only currently running agents. Completed subagents therefore leave stdio MCP processes/pipes attached to the long-lived root until it approaches the 256 FD limit.

    Expected

    Subagent completion should tear down its stdio MCP stack, or the parent should converge to a bounded baseline. A completed subagent should not leave enough pipes behind to prevent unrelated local commands from spawning.

    Local mitigation

    I reduced max_concurrent_threads_per_session from 1000 to 16. This limits impact but does not fix teardown. No private logs or repository data are attached.

  16. Turnstyle commented on Jul 17, 2026

    @Turnstyle

    I am seeing the same MCP lifecycle leak with:

    • Codex Desktop: 26.707.91948 (build 5440)
    • Embedded Codex CLI: 0.144.5
    • macOS: 26.5.2 (build 25F84)
    • Hardware: Apple silicon with 128 GiB RAM

    After completed subagent work, approximately 170 MCP-related processes remained alive. Their summed RSS was approximately 16.5 GiB. I understand summed RSS is an upper bound because shared pages may be counted repeatedly; nevertheless, MCP process counts and memory use increase with repeated subagent fan-out and do not return to the pre-fan-out baseline.

    Observed sequence:

    1. A parent Codex task spawns several subagents.
    2. Each subagent initializes an MCP helper bundle.
    3. The subagents finish and return their results.
    4. Their MCP helpers remain alive under the long-lived Codex/app-server process tree.
    5. The helpers appear mostly idle but continue accumulating.

    Expected behavior: when a subagent completes or is closed, Codex should close its MCP stdin connections and terminate its owned MCP process group, or safely reuse a bounded shared connection pool.

    I can provide sanitized before/after measurements and process-tree evidence if useful. I will exclude credentials, environment variables, private filesystem paths, and proprietary repository information.

  17. EthanSK commented on Jul 18, 2026

    @EthanSK

    Current-build corroboration: Desktop 26.715.31925 / CLI 0.145.0-alpha.18

    This remains reproducible on a newer unified ChatGPT/Codex macOS build. This snapshot was gathered read-only; I did not terminate helpers, edit SQLite state, change configuration, or restart the app.

    Environment

    • ChatGPT/Codex App: 26.715.31925 (build 5551)
    • Bundled Codex CLI: 0.145.0-alpha.18
    • Platform: macOS 26.5.2 (25F84), Darwin 25.5.0 arm64 arm
    • Physical RAM: 36 GiB
    • Enabled local stdio helpers relevant to this snapshot:
      • global @playwright/mcp@latest
      • project-scoped nx mcp
      • built-in node_repl

    Live process and memory evidence

    A single persistent Desktop app-server owned repeated helper roots:

    18 x npm exec @playwright/mcp@latest
    15 x npm exec nx mcp
    19 x node_repl
    

    The direct roots alone summed to approximately:

    Playwright launchers: 605.9 MiB RSS
    Nx launchers:         468.6 MiB RSS
    Node REPL hosts:      136.7 MiB RSS
    Total direct roots:  1,211.2 MiB RSS
    

    Walking the complete descendant trees for these three helper families found 121 processes using approximately 3.63 GiB summed RSS. Summed RSS is an upper bound because shared pages can be counted more than once, but the process multiplier is unambiguous.

    The persistent app-server itself was approximately 818 MiB RSS in the same investigation. macOS reported 8.18 GiB swap in use out of 9 GiB allocated. Current memory pressure had recovered to 61% free, so this was not a claim that the host was actively OOM at capture time.

    The helper roots appeared in repeated timestamp-aligned clusters. Most clusters contained one Playwright launcher, one Nx launcher, and one Node REPL host, consistent with a fresh per-session MCP initialization rather than one shared project/runtime pool.

    Local log evidence

    The current logs_2.sqlite contains explicit task teardown events such as:

    Agent loop exited
    Shutting down Codex instance
    clearing thread listener during thread-state teardown
    

    Four such teardown sequences occurred between 19:35:08 and 19:37:09 local time. The helper clusters remained alive under the same app-server afterward.

    The rmcp::transport::child_process target had no matching child-exit records during the current app-server lifetime. Its most recent exit records were from the previous app-server lifetime and showed Playwright, Nx MCP, and Node REPL exiting around a prior resume/restart boundary. This is consistent with full app-server termination cleaning the tree while ordinary task/subagent shutdown does not.

    The logs also show the initialization path:

    thread/resume
      -> thread_spawn
      -> session_init
      -> session_init.mcp_manager_init
      -> start_server_task
    

    Persisted lifecycle evidence

    The active state_5.sqlite.thread_spawn_edges table contained:

    status=open:   164
    status=closed:  36
    parents with open children: 49
    largest open-child count for one parent: 58
    

    The 164 open child rows were not archived. This independently supports the stale subagent lifecycle problem tracked in #33700.

    Crash evidence boundary

    I found historical Crashpad sidecars, including renderer and browser process types, but no matching renderer crash in the two-hour unified-log window used for this live memory snapshot. I am reporting the process/RAM lifecycle leak here, not claiming that this specific snapshot ended in a fresh crash.

    Expected behavior

    • Task/subagent shutdown should explicitly close each session's MCP clients and terminate the complete stdio process groups.
    • Completed or interrupted subagents should transition their thread_spawn_edges rows to closed.
    • Restoring or resuming tasks should not recreate stale helper generations.
    • Helper counts and memory should converge to a bounded baseline without requiring a full Desktop restart.
    • Diagnostics should expose the owning task/session and lifecycle state for every MCP root.

    PR #19753 was merged as a shutdown cleanup fix, but this later build still reproduces the leak on the task/subagent teardown and restore paths.

    Related: #33700, #30408, #32942.

    I have not attached raw logs, SQLite databases, session transcripts, environment variables, task IDs, prompts, repository names, or personal filesystem paths because those artifacts may contain private information. I can provide narrower sanitized queries or process snapshots if a maintainer needs them.

  18. philkunz commented on Jul 19, 2026

    @philkunz

    Independent sanitized Linux reproduction of the Playwright MCP lifecycle leak.

    Environment

    • codex-cli 0.144.5
    • Linux x86_64
    • Long-lived codex app-server --listen unix:// process, approximately 27 hours old
    • Global stdio server command observed as npm exec @playwright/mcp@latest

    Process evidence

    • The live app-server had accumulated 61 direct npm exec @playwright/mcp@latest children.
    • Each launcher had the expected shell and playwright-mcp Node descendant.
    • Creation times were spread across the app-server lifetime, including launchers older than one day and multiple timestamp-aligned bursts during later activity. Earlier generations remained alive and mostly idle.
    • There were not 61 browser instances. Only five headless Chromium trees were running, each with a distinct ephemeral playwright_chromiumdev_profile-* user-data directory. This confirms separate MCP generations do not share one browser by default, while many MCP launchers may never have launched a browser.
    • The app-server itself later degraded into a high-CPU Tokio worker loop and would not stop on SIGTERM; this is cross-reported in app-server: degraded broker catches SIGTERM then deadlocks on a futex (needs SIGKILL); healthy broker exits <1s #34142.
    • After forced app-server termination, the MCP launcher population disappeared, but the five Chromium trees survived as PID-1 orphans because SIGKILL bypassed normal cleanup.

    The direct-parent relationship, spread of process ages, and retained earlier generations match the missing per-session/subagent MCP teardown described in this issue. No project paths, hostnames, account data, configuration contents, browser profile identifiers, or session data are included.

  19. CodeGlitch commented on Aug 16, 2026

    @CodeGlitch

    Confirmed on Windows x64 with Codex Desktop 26.810.7004.0 and global @playwright/mcp@latest stdio configuration. After Playwright tasks had finished and no browser automation session was active, 372 Playwright-related processes remained. Terminating only those processes reclaimed about 12.18 GB. The process trees later began accumulating again. No session transcript or private project data is included.

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

    browserbugSomething isn't workingmcpIssues 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