Codex.app GUI: MCP child processes not reaped after task completion — 1300+ zombies, 37GB memory leak

Open 💬 26 comments Opened Feb 22, 2026 by rolldav
💡 Likely answer: A maintainer (github-actions[bot], contributor) responded on this thread — see the highlighted reply below.

Codex.app GUI: MCP child processes not reaped after task completion — 1300+ zombies, 37GB memory leak

What version of Codex CLI is running?

codex-cli 0.98.0 (Codex.app GUI wrapper)

What subscription do you have?

ChatGPT Pro

Which model were you using?

o3 (via Codex.app GUI)

What platform is your computer?

macOS 26.3 (25D125), Apple M4 Max, 64GB RAM, arm64

What issue are you seeing?

Codex.app spawns codex exec --full-auto worker processes for each worktree but never reaps them or their MCP child processes when tasks complete. Over multiple audit sessions, this accumulated 1,319 zombie Codex processes and 1,537 node processes consuming \~37GB RSS + 40GB swap on a 64GB machine, causing severe system-wide slowdown.

Root cause analysis

Each Codex.app task creates:

  • codex exec --full-auto --skip-git-repo-check --json - (CLI worker)
  • oh-my-codex MCP servers (state, memory, trace, code-intel)
  • 1× Serena MCP server (via uv tool uvx)
  • uv process for Serena

≈ 7 processes per task. None are killed when the task completes.

With 12 git worktrees created by Codex.app (detached HEAD, pointing at old commits), this produced:

  • 12 worktrees × \~110 child processes = \~1,320 sleeping processes
  • Combined RSS: \~37GB (mostly npm/node/uv MCP servers)
  • Swap usage: 39.4GB (on 64GB physical RAM)

When Codex.app (PID 19947) was killed, 2 additional codex exec workers (launched Friday and Saturday) were reparented to launchd (PPID=1), confirming they were never properly cleaned up.

Process tree snapshot (before cleanup)

Codex.app (PID 19947, 279MB)
├── codex exec --full-auto (PID 19970, 123MB)
│   └── 880× node/npm/uv children (27GB total)
│       ├── npm (236MB each, ×2)
│       ├── npm (140MB each, ×4)
│       ├── uv (91MB each, ×2)
│       └── node (77MB each, ×dozens)
├── Codex Helper Renderer (PID 20018, 602MB)
└── Codex Helper (PID 19971, 101MB)

Orphaned (PPID=1, reparented from dead Codex.app sessions):
├── codex exec --full-auto (PID 59326, Fri 23:59)
│   ├── oh-my-codex/state-server.js
│   ├── oh-my-codex/memory-server.js
│   ├── oh-my-codex/trace-server.js
│   ├── oh-my-codex/code-intel-server.js
│   └── serena start-mcp-server
└── codex exec --full-auto (PID 59747, Sat 22:05)
    └── (same MCP stack)

Memory impact

<!-- linear:table-colwidths:200,200,200,200 -->
| Metric | Before cleanup | After cleanup | Delta |
| -- | -- | -- | -- |
| Swap used | 39.4 GB | 8.0 GB | -31 GB |
| Codex processes | 1,319 | 0 | -100% |
| Node processes | 1,537 | 17 | -99% |
| Free pages | 82K | 2.3M | ×28 |

Worktree state at time of discovery

All 12 worktrees were detached HEAD on old commits (2+ phases behind current main), confirming tasks had completed long ago:

/Users/drg/.codex/worktrees/0585/medical-ai  627d44e (detached HEAD)
/Users/drg/.codex/worktrees/0b7b/medical-ai  627d44e (detached HEAD)
/Users/drg/.codex/worktrees/2a60/medical-ai  4d53e7d (detached HEAD)
... (12 total, all on 2 old commits)

Current HEAD: 71aefff — none of the worktrees were anywhere near current.

What steps can reproduce the bug?

  1. Use Codex.app GUI to run multiple tasks on a git repository (especially with MCP servers configured via oh-my-codex)
  2. Let tasks complete normally
  3. Wait a few hours / run more tasks
  4. Check process count: ps aux | grep -c codex
  5. Check memory: ps aux | grep codex | awk '{sum+=$6} END {printf "%.0f MB\n", sum/1024}'

Accumulation is proportional to number of tasks × MCP servers configured.

What is the expected behavior?

When a Codex.app task completes:

  1. The codex exec worker process should be terminated
  2. All MCP child processes (oh-my-codex servers, Serena, uv) should be killed via process group signal
  3. The git worktree should be cleaned up if the task is done (or at minimum, child processes should not persist)

What is the actual behavior?

  • Worker processes and their entire MCP subtree remain alive indefinitely
  • No cleanup occurs on task completion, app quit, or even app crash (reparented to launchd)
  • Memory grows unboundedly with each task

Relationship to existing issues

  • CLI-494 (fixed, Oct 2025): start_kill() without wait() in exec.rs — same root cause pattern (missing process reaping), but fix only covered timeout/Ctrl+C in CLI, not GUI lifecycle
  • CLI-3017 (Jan 2026): CLI memory leak causing crashes
  • [#12414](<https://github.com/openai/codex/issues/12414>) (Feb 2026, open): Unbounded memory growth when idle on v0.104.0

This issue is distinct because it specifically affects Codex.app GUI and its interaction with MCP server child processes — the GUI spawns long-lived codex exec workers that themselves spawn MCP servers, creating a deeper process tree that the existing cleanup logic does not handle.

Suggested fix

  1. Process group kill: codex exec workers should use setsid() / process groups so all children (MCP servers) can be killed with a single killpg() on task completion
  2. Worktree lifecycle: Codex.app should track active worktrees and clean up stale ones on startup (e.g., remove worktrees whose tasks are no longer active)
  3. Heartbeat/watchdog: MCP servers spawned by codex exec should monitor their parent and self-terminate if the parent dies (check PPID periodically, or use a pipe/fd that breaks on parent death)

View original on GitHub ↗

26 Comments

github-actions[bot] contributor · 4 months ago

Potential duplicates detected. Please review them and close your issue if it is a duplicate.

  • #11324
  • #12335
  • #12333
  • #11090

Powered by Codex Action

vincentkoc contributor · 4 months ago

I put up a draft fix for this path: https://github.com/openai/codex/pull/12505

Scope is intentionally narrow and low-risk:

  • adds features.thread_listener_cleanup as an experimental flag (default off)
  • clears per-thread listener state when subscriber count drops to zero (when flag is enabled)
  • always clears app-server thread state during archive teardown
  • avoids event serialization work when there are no subscribers

I am currently seeing memory pressure in app usage with 5+ concurrent threads, so this is aimed at reducing retained in-memory thread/listener state without changing default behavior.

vincentkoc contributor · 4 months ago

Given the updated contribution policy (invitation-only PRs), #12505 is currently a draft patch proposal to make discussion concrete. If this direction aligns with maintainer intent, would someone from the team be willing to explicitly invite that PR (or suggest a narrower scope)? I can provide additional repro/profiling details from macOS sessions where memory pressure climbs with 5+ concurrent threads.

gabrielbryk · 4 months ago

Experiencing this on Linux / codex-cli 0.106.0

Reproducing the same issue. Here's a breakdown of the two distinct leak patterns I'm seeing:

Environment:

  • codex-cli 0.106.0
  • Ubuntu 25.10, Linux 6.17.0-14-generic x86_64

Crash: failed to spawn thread: Os { code: 11, kind: WouldBlock, message: "Resource temporarily unavailable" }

Two distinct leak patterns observed:

  1. Zombie children not reaped (missing waitpid): A single long-running codex session accumulated 41 zombie <defunct> children — all from a Python stdio MCP server. The MCP server forks subprocesses for tool calls, but the codex parent never calls waitpid() to reap them. pstree output:

``
codex,49983
|-(MainThread,105811) <defunct>
|-(MainThread,125138) <defunct>
|-(MainThread,131433) <defunct>
... (41 total zombies)
``

  1. MCP server processes not terminated on session exit: When a codex session ends or is interrupted, its stdio MCP child processes are never sent SIGTERM. They persist indefinitely. I found 8 separate MCP process trees (one per stale codex session), each still running. These are live processes consuming real resources, not just zombies.

The combination of both leaks led to thread exhaustion and the crash. This appears to be the same root cause as #4726 (closed) and #6201 (closed) — neither of which fully resolved the lifecycle management for stdio MCP servers.

masvelio · 4 months ago

I can confirm a very similar issue on macOS, and I captured a full CLI process snapshot on March 11, 2026.

Environment

  • Codex App version: 26.305.950
  • macOS: 14.3.1 (23D60)
  • Usage pattern: multiple Codex App projects/threads open over time
  • MCPs in use: context7, stripe, playwright

What I observed

  • The machine was not in a peak CPU spike at the exact time of capture, but memory pressure was already high:
  • 607 total processes
  • 5760 threads
  • 15G used, 74M unused
  • 6608M compressor
  • 6342612 swapins
  • 8519545 swapouts
  • The most important part is the process tree under a single Codex desktop app-server PID:
  • codex app-server PID 60306
  • direct children of PID 60306: 73
  • of those, 72 were MCP launchers
  • exact breakdown:
  • 24 x npm exec @upstash/context7-mcp
  • 24 x npm exec @stripe/mcp --tools=all
  • 24 x npm exec @playwright/mcp@latest
  • including descendants, that subtree contained 148 processes total

This does not look like a bounded “currently active work only” baseline. It looks like repeated MCP launcher accumulation over time under the same long-lived codex app-server.

Long-lived duplicated MCP launcher examples

  • about 8h old:
  • npm exec @playwright/mcp@latest
  • npm exec @stripe/mcp --tools=all
  • npm exec @upstash/context7-mcp
  • about 4h old:
  • another full trio of playwright / stripe / context7
  • about 3h old:
  • more repeated trios
  • about 1h old:
  • more repeated trios
  • about 50m old:
  • more repeated trios
  • about 20h+ old:
  • more repeated trios still attached to the same app-server

Important distinction

  • I understand from #12333 that duplicate MCP instances may be expected while subagents/threads are active.
  • What seems wrong here is lifecycle / teardown:
  • the desktop app is not converging back to a small stable baseline
  • stale MCP launchers keep accumulating under the same codex app-server PID over many hours

This feels closely related to:

  • #12491
  • #11324
  • #12976
  • possibly the earlier stdio MCP cleanup work in #10710

Question for maintainers

  • Should the process-group cleanup merged in #10710 have covered this desktop app path as well?
  • Or is there still a separate cleanup gap in the desktop app / app-server lifecycle?

Thanks for taking a look. The snapshot seems to point to unbounded MCP process accumulation over time rather than short-lived fanout from currently active work, so hopefully it helps narrow the cleanup path.

RemarkRemedy · 4 months ago

This looks related to #11324, but I wanted to add a narrower app-server angle that may help isolate root cause.

I traced a reproducible case where the leaking MCP children are not orphaned from dead workers; instead they remain attached to the long-lived desktop codex app-server process after conversations are no longer active.

The key detail from source review is that MCP servers are conversation-scoped and the intended unload path is thread/unsubscribe. When the last subscriber unsubscribes, the app-server shuts down the thread and unloads it. However, on connection removal the current app-server code path retains zero-subscriber threads instead of unloading them, so their McpConnectionManagers never drop and the stdio MCP child processes stay alive.

I posted the fuller write-up and evidence here:

So this may be the same family of leak as this issue, but specifically through desktop thread lifecycle / unload behavior rather than only worker reaping.

dorukardahan · 4 months ago

Same bug family on my Mac

@masvelio and @gabrielbryk this looks very close to what I hit. @RemarkRemedy also makes sense to me here, but i think there is one more phase after that app-server retention phase.

My setup

  • Codex.app 26.311.21342 (build 993) when this built up
  • macOS Darwin 25.3.0 on Apple Silicon
  • app was open about 2.5 days
  • about 18 separate conversations/tasks in that window
  • MCPs involved: context7, playwright, chrome-devtools, webflow mcp-remote, and one custom Python MCP

What I saw

~100 Node processes
20+ Python processes
~5 MCP servers per conversation
18 conversation generations x ~5 MCPs = ~90 stale MCP processes

Each conversation was leaving its own MCP bundle under the long-lived Codex.app -> codex app-server tree. Finished conversations were not going back to a small baseline.

The extra part on my side

The main thing i did not see called out clearly yet is what happened on quit.

When I quit Codex from the Dock on March 17, 2026, most children died, but cleanup was only partial:

  • Node count dropped from about 100 to about 10
  • some MCP processes survived and were reparented to PID 1 / launchd
  • I found survivors still alive from March 9, 2026
  • I also had two old Codex chrome_crashpad_handler processes still alive from March 9 and March 14

I also saw what looks racey. In one spawn generation, most siblings from that batch were gone, but one twitterapi-docs-mcp survivor was still there by itself.

Why I think this matters

#10710 makes me think the stdio process-group cleanup on drop exists already. So maybe there are two gaps:

  1. old conversation state is sometimes not getting dropped in the desktop app
  2. when quit starts, some children still escape the final teardown path

One control point from the same machine: Claude Code via Superset.app only had about 11 MCP processes from 3 active sessions, and those were cleaned up normally. So this did not look like a generic macOS "too many MCPs" problem.

dorukardahan · 4 months ago

Small timing clarification on my earlier comment

One detail i want to tighten up from my earlier note here:

The orphan set i described was real right after Cmd+Q on March 17, 2026.
At that moment, most Codex child processes died, but i still had a small PID 1 / launchd residue:

2x `chrome_crashpad_handler`
2x orphaned `playwright-mcp`
1x orphaned `twitterapi` MCP

Later, after I reopened Codex and closed it again, most of those leftovers finally disappeared.

So my main point is not "every orphan stayed forever".
It is: one quit cycle still left a small orphan set when the expected number should have been zero.

That still feels consistent with the two-part picture @gabrielbryk and @RemarkRemedy were circling around:

  • accumulation while the app-server stays alive
  • then nondeterministic cleanup on quit, where most of the tree dies but a few children can still escape
whatsgrowth · 3 months ago

Fix implementation available

I hit this exact issue on a 16GB RAM Linux machine — running multiple Codex sessions with MCP servers (desktop-commander, context7, playwright, exa) accumulated 562 orphaned processes consuming 26GB of swap, triggering OOM kills and complete system freezes.

After investigating the source, I traced the root cause and implemented a fix. The branch is here:
👉 https://github.com/whatsgrowth/codex/tree/fix/mcp-process-cleanup (diff)

Root cause

McpConnectionManager holds a HashMap<String, AsyncManagedClient> but has no explicit cleanup. Wrapped in Arc<RwLock<>> inside SessionServices, the Arc<Session> reference count may never reach zero (held by spawned tasks, event channels, etc.), so ProcessGroupGuard::drop() never fires — and MCP server processes live forever.

The low-level cleanup already works correctly: ProcessGroupGuard in rmcp_client.rs properly implements Drop with killpg(SIGTERM) → 2s grace → killpg(SIGKILL). The problem is simply that it's never triggered.

The fix (48 additions, 0 deletions, 2 files)

1. core/src/mcp_connection_manager.rs — Add shutdown() method:

pub(crate) fn shutdown(&mut self) {
    self.clients.drain();
}

2. core/src/codex.rs — Call it in Session::shutdown():

sess.services.mcp_startup_cancellation_token.lock().await.cancel();
sess.services.mcp_connection_manager.write().await.shutdown();

3. core/src/mcp_connection_manager.rs — Safety net Drop:

impl Drop for McpConnectionManager {
    fn drop(&mut self) {
        if !self.clients.is_empty() {
            warn!("McpConnectionManager dropped with {} active client(s); cleaning up", self.clients.len());
            self.clients.drain();
        }
    }
}

4. core/src/codex.rs — Also clean up in refresh_mcp_servers_inner:

manager.shutdown();
*manager = refreshed_manager;

Verified

  • cargo check -p codex-core
  • cargo test -p codex-core --lib -- shutdown — 7/7 pass ✅
  • Manual test: started Codex with MCP servers, ran a task, exited → pgrep confirmed all MCP processes were killed on exit ✅

Could not open a PR directly (repo restricted to collaborators), so posting the fix here. Happy to rebase if needed.

jaegerstrength · 3 months ago

Additional macOS desktop-app evidence for this issue on April 6, 2026.

I verified a live Codex.app session where this is happening with exactly one configured Playwright MCP server in ~/.codex/config.toml:

[mcp_servers.playwright]
command = "npx"
args = ["@playwright/mcp@latest"]

Current live parent process:

  • /Applications/Codex.app/... codex app-server --analytics-default-enabled
  • PID 68203

All leaking Playwright MCP descendants are parented to that single live app-server, not to old dead app instances.

Observed process tree totals:

  • 55 npm exec @playwright/mcp@latest wrapper processes
  • 55 node .../playwright-mcp child processes
  • 110 total Playwright MCP descendants

Observed RSS:

  • wrappers: about 241.6 MB
  • node children: about 106.8 MB
  • total descendants: about 348.4 MB

Important details:

  • there is only one configured Playwright MCP server entry, so this is not duplicate config
  • the processes appear in repeating wrapper/child pairs over time during one live desktop-app session
  • restarting Codex clears the pile temporarily, but a new app-server begins accumulating fresh Playwright MCP descendants again

This looks consistent with an app-side stdio MCP lifecycle/reaping bug rather than a user config problem or a one-off stale subtree.

Related issue that also seems relevant from the Playwright side: #14773.

ZWhiteTrace · 3 months ago

Still reproducing on latest versions as of 2026-04-15.

Environment

  • Codex.app 0.119.0-alpha.28
  • Homebrew codex CLI 0.120.0 (invoked as codex mcp-server by an external client)
  • macOS 15.5 (24F74), Apple Silicon

Evidenceps -eo pid,etime,command:

89227       04:10  codex mcp-server
 9239    04:32:55  codex mcp-server
35505    05:56:57  codex mcp-server
63171    11:34:26  codex mcp-server
41760    01:06:22  node .../codex-playwright-mcp-cache/.../playwright-mcp
78476    09:20:59  node .../codex-playwright-mcp-cache/.../playwright-mcp
 7298    11:13:36  node .../codex-playwright-mcp-cache/.../playwright-mcp
75105    11:29:09  node .../codex-playwright-mcp-cache/.../playwright-mcp

4 playwright-mcp children, oldest at 11h29m. 4 codex mcp-server parents, oldest at 11h34m. None correspond to an active session; they are leaked from sessions that ended hours ago. /tmp/codex-playwright-mcp-cache at 253 MB.

Consistent with the 2026-04-06 Playwright-MCP comment above — still present on 0.119/0.120.

RedesignedRobot · 3 months ago

Still reproduces on v0.120.0 / Codex Desktop 26.409.20454. We observed 213 leaked @playwright/mcp process pairs (426 processes, 13.6 GB RSS). Filed as #17832.

PR #17223 (linked to #16895, merged April 10) fixed WebSocket client disconnect only. Subagent MCP teardown remains broken. See also #17574.

Root cause analysis with proposed fix: #17832 (comment)
https://github.com/openai/codex/issues/17832#issuecomment-4247026597

Update (2026-04-19): patch ready on branch RedesignedRobot:codex:fix/mcp-subprocess-cleanup, rebased on current main, cargo check -p codex-core -p codex-mcp clean. 2 files, +23/-2, covers both the Op::Shutdown path and the submission_loop channel-close fallthrough. Awaiting invitation to open as PR per the invitation-only contribution policy.

luohoa97 · 3 months ago

I get the memory leak on the Linux codex app port as well https://github.com/EvickaStudio/codex-app-linux-port
It spawns zombie MCP processes that eat 8gb of ram, my system regularly has to kill my browser tabs to keep the system from crashing. I think it's related to playwright as I have playwright as well.

To reproduce:

  • Install the codex app 26.409.20454
  • Install playwright MCP
  • Open multiple conversations with the MCP server enabled
  • Ram usage should leak over time with more zombie processes being created and you should get 8gb used over a few conversations.
m13v · 3 months ago

hit the same class of leak running a bunch of stdio MCP servers on macOS. the real culprit is process group handling, if you spawn the child without setsid/setpgid the parent can't send SIGTERM to the whole tree on exit, so npm/uv wrappers leak their node grandchildren. also node/npm wrappers don't forward SIGTERM to the real mcp binary by default, you need to exec the server directly or wrap with tini. and any stdio mcp needs an EOF-on-stdin watchdog inside the server so it self-exits if the parent dies without signaling. zombie reaping is table stakes but won't fix the reparented-to-launchd survivors, only pgid teardown does.

luohoa97 · 3 months ago

<img width="2481" height="1169" alt="Image" src="https://github.com/user-attachments/assets/49a0f852-aec3-4693-9f0a-8672ed531ce5" />
I got a new codex client so this is a different reproducibility process.

  1. Clone https://github.com/ilysenko/codex-desktop-linux
  2. Run the rpm build script
  3. Launch codex
  4. You should get 26.416.11627
  5. Use it for a while and you should get the leak.

Note: Chromium is running on my system as a flatpak and codex isolates the codex renderer and MCP processes.

luohoa97 · 3 months ago

Personally I just disabled the oh my codex MCP servers to save memory and slow down the accumulation of zombie processes. As for every conversation it spawns more duplicate zombie MCP processes that never get freed. This is a bug where for each conversation it just creates MCP servers, but it should be shared between conversations on start.

bingyizhou6u · 2 months ago

I’m seeing what looks like the same Codex Desktop MCP lifecycle leak on macOS.

Environment:

  • Codex Desktop app, macOS
  • Observed Codex app-server process: /Applications/Codex.app/Contents/Resources/codex app-server
  • MCPs involved: chrome-devtools-mcp, gitnexus mcp, Codex node_repl

Symptoms:

  • Codex Desktop repeatedly accumulates local MCP / REPL child processes under the same app-server.
  • This is not just normal Chrome multi-process behavior. The large process count is mostly MCP wrappers and node-based MCP servers launched by Codex.
  • The process count grows again after manual cleanup and continued Codex usage.

Observed before cleanup:

  • chrome-devtools-mcp: about 368 matching processes
  • gitnexus mcp: about 186 matching processes
  • node_repl: about 93 matching processes
  • chrome-devtools-mcp alone was roughly 19 GB RSS in one snapshot
  • gitnexus mcp was roughly 8 GB RSS in one snapshot

Relevant process pattern:

/Applications/Codex.app/Contents/Resources/codex app-server
  ├─ npm exec chrome-devtools-mcp@latest --channel stable --viewport 1440x960 --no-usage-statistics
  │   └─ chrome-devtools-mcp
  ├─ node /opt/homebrew/bin/gitnexus mcp
  └─ /Applications/Codex.app/Contents/Resources/node_repl

Manual cleanup results:

  • Killing stale chrome-devtools-mcp, gitnexus mcp, and node_repl processes immediately reduced the process count.
  • After cleanup, the system returned to roughly:
  • chrome-devtools-mcp: 0 initially, later restarted only when needed
  • gitnexus mcp: 2
  • node_repl: 1
  • Regular Google Chrome browser processes were not the source of the large count and were not killed.

Expected behavior:

  • Codex Desktop should either reuse MCP server processes or reliably terminate per-session MCP/REPL stacks when a session/thread/subagent no longer needs them.
  • Background or stale conversations should not leave hundreds of MCP server processes alive.

Actual behavior:

  • MCP/REPL processes accumulate under Codex app-server over time.
  • Manual cleanup is required to recover memory and process count.
leojkwan · 2 months ago

+1 confirming this is still active on Codex Desktop 26.422.x as of 2026-04-26, with a different MCP/feature manifestation worth naming explicitly: the Computer Use subsystem under codex app-server. This reinforces what @bingyizhou6u flagged on 2026-04-25 with concrete pid-tree numbers.

Environment

  • macOS Darwin 25.3 (Sequoia), Apple Silicon, 64 GB RAM
  • Codex Desktop, Computer Use enabled, ~17h continuous uptime
  • 1 active codex app-server --analytics-default-enabled parent

Process tree at peak

  • ~100 Codex Computer Use.app/Contents/SharedSupport/SkyComputerUseClient (4–16 MB RSS each)
  • ~30 Codex.app/Contents/Resources/node_repl (4–7 MB RSS each)
  • All parented under the single codex app-server pid (not adopted by launchd, not detached)
  • Healthy steady-state observed post-cleanup: ~4 of each (the active worker pool)

Why Activity Monitor shows 24+ GB for "Codex"
The individual RSS numbers above are tiny — the headline number is macOS's compressor pool, not resident memory. vm_stat showed:
\\\
Free: 1.0 GB
Active: 14.2 GB
Inactive: 14.1 GB
Wired: 8.0 GB
Compressor: 25.7 GB ← here
Swap I/O: 66K in / 399K out
\
\\
Compressed pages get attributed back to whichever process owned them — so 100+ small worker working sets that got compressed under pressure and never freed showed up as a single 24.5 GB "Codex" attribution and triggered the macOS "Your system has run out of application memory" Force-Quit dialog.

Workaround (narrowly-targeted, does NOT touch Codex.app, codex app-server, the active worker pool, or any other process)
\\\bash
pkill -f 'Codex Computer Use.app/Contents/SharedSupport/SkyComputerUseClient'
pkill -f 'Codex.app/Contents/Resources/node_repl'
\
\\
After running, free RAM moved 1.0 → 2.7 GB instantly; Codex respawned the correct ~4 workers from the active pool. Confirms these are leaked, not active.

Repro signal (one-liner check):
\\\bash
ps -axo pid,command | grep -cE 'SkyComputerUseClient|node_repl'
\
\\
Healthy: 4–8. Leak active: 20+ and growing with uptime. Counts grow monotonically with Computer Use automation runs over time.

Suggested fix surface (consistent with #18881 and #17832): whatever invokes the Computer Use MCP tool needs to child.wait() / SIGTERM the spawned worker pair when the tool call resolves, the same lifecycle gap the broader MCP-reaping issue describes — Computer Use is just one more affected MCP host in the same pattern.

ideaitor · 2 months ago

Adding evidence on two more stdio MCPs in the same leak family on Codex Desktop.

Environment

  • Codex.app 26.422.30944 on macOS Darwin 24.6.0, 10-core arm64, 24 GB RAM
  • One live codex app-server --analytics-default-enabled parent (PID 9529)
  • Configured stdio MCPs: playwright, lighthouse (local), supabase, heroku

Affected MCPs (in addition to those already reported)

heroku (heroku mcp:start):

  • Spawn chain: bash heroku mcp:start → 3 levels of bash wrappers → node run mcp:startnode heroku-mcp-server.mjs
  • The CLI also fires heroku plugins:inspect @heroku/plugin-ai as part of plugin discovery on every startup
  • After ~45 minutes of normal use, observed:
  • 8 heroku mcp:start bash wrappers parented to app-server
  • 8 heroku-mcp-server.mjs node processes
  • 4 npm exec heroku@latest --repl
  • 12 heroku plugins:inspect @heroku/plugin-ai processes — many with PPID=1 (parent bash chain died, signals not propagated; orphans reparented to launchd)
  • Combined: ~80 leaked heroku-related processes, ~250% CPU, ~2 GB RAM
  • This compounds the issue: even if the Codex side adds setsid/killpg, Heroku's bash wrapper chain detaches plugins:inspect from the process group, so existing orphans wouldn't be cleaned up retroactively. New spawns under a process group would be fine.

lighthouse (local stdio MCP):

  • Single node /Users/steve/.codex/lighthouse-mcp-local/server.js config
  • After same window: 12 lighthouse-mcp-local/server.js processes + 7 Codex.app/Contents/Resources/node_repl processes, all parented to the live app-server
  • Each node_repl appears paired with a lighthouse spawn — confirms what bingyizhou6u and leojkwan reported about node_repl being part of the stdio MCP host pattern

System impact at peak

  • Load avg: 39.20 on a 10-core box
  • CPU idle: 0.10%
  • Free RAM: 583 MB (heavy swap thrashing — 15.9M swapins / 17.6M swapouts)
  • Codex Helper (Renderer) at 149% CPU — mostly stuck waiting on the dying MCP IPC

Workaround applied

  • Set enabled = false on [mcp_servers.heroku] and [mcp_servers.lighthouse] in ~/.codex/config.toml
  • pkill -f "lighthouse-mcp-local/server.js", same for heroku patterns
  • Load dropped 39 → 4.7, free RAM recovered immediately
  • Running a local watchdog (5-min launchd job) that detects duplicates of allowlisted MCP commands and kills all but the newest PID, until the upstream fix lands

Notes for the suggested fix
The m13v comment on this issue covers the right surface. Adding two practical points from the heroku case:

  1. setsid + killpg in Codex would solve it cleanly — even with Heroku's leaky bash chain, every descendant inherits the process group at fork time.
  2. For stdio MCPs whose CLI wrappers chain through bash without exec, an EOF-on-stdin watchdog inside the MCP server (per m13v) is the only thing that will reliably kill currently-running orphans whose parent died ungracefully. setsid/killpg is the right primary fix; the EOF watchdog is a robustness backstop.

Happy to provide more raw ps output or repro logs if useful.

Panlelaila · 2 months ago

I can reproduce a closely related variant of this on macOS, but specifically with the bundled Computer Use helper processes.

Environment:

  • Codex App: 26.422.30944 (2080)
  • macOS: 26.3.1 (25D2128)
  • Architecture: arm64
  • Bundled plugin: computer-use@openai-bundled / computer-use plugin version 1.0.758

What I observed on April 28, 2026:

  • ~/.codex/config.toml had plugins."computer-use@openai-bundled".enabled = true
  • Codex had accumulated 107 instances of:

./Codex Computer Use.app/Contents/SharedSupport/SkyComputerUseClient.app/Contents/MacOS/SkyComputerUseClient mcp

  • All of them were children of the same desktop app process subtree:

/Applications/Codex.app/Contents/MacOS/Codex
-> /Applications/Codex.app/Contents/Resources/codex app-server --analytics-default-enabled
-> many SkyComputerUseClient mcp

  • The helper binary path resolved to the bundled plugin cache:

~/.codex/plugins/cache/openai-bundled/computer-use/1.0.758/.../SkyComputerUseClient

  • Combined RSS for those SkyComputerUseClient processes alone was about 1.26 GB (average ~11.8 MB each).
  • Several of the helpers had been alive since the same app session that started around 2026-04-28 08:10, so these were not short-lived one-shot helpers.

Important narrowing signal:

  • I manually cleaned them up with pkill against SkyComputerUseClient / SkyComputerUseService.
  • After cleanup, I re-ran only ordinary non-Computer-Use shell actions inside Codex (pwd, date, sed ... package.json) and repeatedly re-checked process counts.
  • The count stayed at 0, and neither SkyComputerUseClient nor SkyComputerUseService immediately came back.

That makes this look narrower than “all Codex activity leaks MCP children”.
It looks more like a lifecycle / teardown bug in the bundled computer-use plugin path, or in how app-server manages that MCP/helper subtree after the helper has been activated.

So this may be the same general cleanup class as this issue, but with a more specific reproduction target:

  • desktop app
  • computer-use@openai-bundled
  • SkyComputerUseClient mcp
  • lingering under codex app-server after prior usage instead of being reaped

If helpful, I can also provide a process-tree snapshot and exact command output showing the parent/child relationships.

shane9coy · 2 months ago

Additional repro: Computer Use / SkyComputerUseClient CPU fanout

I hit what looks like the same MCP lifecycle/leak family, but with a narrower Computer Use-specific trigger that may help isolate one upstream fix surface.

Environment

  • Codex Desktop: 26.506.31421 (CFBundleVersion 2620)
  • macOS: 26.3.1 (25D771280a)
  • Darwin: 25.3.0, arm64
  • Machine: M3 MacBook Air, 16 GB RAM
  • Computer Use plugin enabled: computer-use@openai-bundled

Symptom

Codex Desktop would spike into the 200-300% CPU range on normal prompt completions. CPU temps went over 220°F and the machine became close to unusable.

At peak I observed:

Codex                       ~248-264% CPU
Codex Helper (Renderer)     up to ~176% CPU
WindowServer                ~28-44% CPU
codex app-server            ~34% CPU during one spike

In one spike, the MCP/process fanout under the same codex app-server also multiplied:

10x SkyComputerUseClient mcp
10x uvx minimax-mcp
10x uvx minimax-coding-plan-mcp
11x relay_mac_arm64 --mcp

The local Codex log DB was also writing heavily during the spike:

~/.codex/logs_2.sqlite-wal: 80M
inserted_rows=1257 over 16 seconds
rows_per_second=78.56

Failure mode 1: SkyComputerUseClient inserted into turn-ended

My ~/.codex/config.toml had been rewritten into this shape:

notify = [
  "/Users/<user>/.codex/computer-use/Codex Computer Use.app/Contents/SharedSupport/SkyComputerUseClient.app/Contents/MacOS/SkyComputerUseClient",
  "turn-ended",
  "--previous-notify",
  "[\"\\/Users\\/<user>\\/.codex\\/scripts\\/codex-notify-wrapper.sh\",\"turn-ended\"]"
]

That means ordinary prompt completions were routed through SkyComputerUseClient before reaching the lightweight previous notifier.

As a result, this path could fire on every normal response:

Codex finishes a normal prompt
-> turn-ended notify hook fires
-> SkyComputerUseClient wakes up
-> Computer Use / MCP / app-server plumbing wakes up
-> Electron renderer / GPU helpers wake up
-> WindowServer gets pulled into extra compositor work

That seems like the wrong place for SkyComputerUseClient. The user-facing notify hook is a high-frequency lifecycle hook for normal prompt completions. It should stay lightweight and bounded.

Workaround for failure mode 1

I backed up ~/.codex/config.toml and restored notify to a lightweight wrapper:

notify = ["/Users/<user>/.codex/scripts/codex-notify-wrapper.sh", "turn-ended"]

Then I fully restarted Codex Desktop to clear the already-spawned MCP/process tree.

After restart and another prompt:

notify stayed clean
1x SkyComputerUseClient mcp
1x Unity relay
1x MiniMax MCP pair
Codex main process around 2-3%
Renderer around 8%
app-server around 14%

Initially, this stopped the largest runaway. Computer Use could remain enabled for a short test. The critical fix for this first failure mode was removing SkyComputerUseClient from the turn-ended notify wrapper path.

Failure mode 2: Computer Use fanout even after notify stays clean

After more testing, I reproduced a second related failure mode. notify stayed clean:

notify = ["/Users/<user>/.codex/scripts/codex-notify-wrapper.sh", "turn-ended"]

But with computer-use@openai-bundled enabled, normal prompt/session activity still caused Computer Use-related children to respawn under codex app-server:

SkyComputerUseClient mcp
relay_mac_arm64 --mcp
uvx minimax-mcp
uvx minimax-coding-plan-mcp
node_repl

This points to a deeper Computer Use / MCP lifecycle problem, not a general claim that all user MCP servers are bad. The important narrowing signal is:

  • the bad notify wrapper is one reproducible trigger
  • even after fixing notify, computer-use@openai-bundled can still recreate SkyComputerUseClient / MCP child-process fanout
  • disabling only Computer Use stopped the SkyComputerUseClient fanout in my local containment test

So I think SkyComputerUseClient should only wake for explicit Computer Use actions. It should not be started by normal prompt completion or ordinary session lifecycle work.

Suggested upstream fix

I think there are three related fixes here:

  1. Do not insert SkyComputerUseClient into notify = ["...", "turn-ended"].

turn-ended fires after normal prompt completions. It should not bootstrap Computer Use / MCP plumbing unless a Computer Use action was explicitly requested.

  1. On startup, detect and migrate this bad config shape back to the previous notifier.

If Codex sees:

``toml
notify = [".../SkyComputerUseClient", "turn-ended", "--previous-notify", "..."]
``

it should unwrap it back to the previous lightweight notifier, or at minimum warn the user and refuse to use SkyComputerUseClient as a lifecycle notify handler.

  1. Tighten Computer Use process lifecycle under codex app-server.

When computer-use@openai-bundled is enabled, SkyComputerUseClient mcp instances should converge back to a small stable baseline after tool/session work. If normal prompt activity can create repeated Computer Use client stacks, those children need explicit ownership, teardown, and process-group cleanup.

This seems related to the broader MCP child lifecycle issue in this thread. The additional narrowing here is that Computer Use / SkyComputerUseClient can reproduce the runaway both through a bad notify wrapper and through plugin lifecycle/fanout even after notify is corrected.

Quick user-side check

Anyone hitting this can check:

rg -n 'notify|SkyComputerUseClient' ~/.codex/config.toml
ps -axo pid,ppid,%cpu,%mem,rss,comm,args | rg 'Codex|SkyComputerUse|relay_mac_arm64|mcp' | rg -v 'rg |/bin/zsh'

If notify starts with SkyComputerUseClient, try backing up the config, restoring notify to the previous lightweight notifier, and fully restarting Codex Desktop.

If notify is clean but SkyComputerUseClient mcp keeps multiplying, try disabling only computer-use@openai-bundled as a containment test. I would not treat unrelated MCP servers as the first suspect unless their own process counts are also multiplying.

etraut-openai contributor · 2 months ago

The bug reported by the OP has been fixed. If you are seeing a similar behaviors in the latest version of the app, please open a new bug report.

shane9coy · 2 months ago

Update after deeper local repro/debugging:

I want to correct the likely root-cause framing from my earlier comment. The Computer Use / MCP child-process fanout may have amplified the issue, but the strongest evidence now points to a huge text-like workspace artifact triggering the Electron/V8 hot path.

In the affected workspace, there was a generated embedded HTML file around 205MB. After moving that file out of the Codex-watched project root, the behavior improved substantially. The file was not an .xlsx issue; the Excel files in the project were small and Codex did not appear to have them open.

My current working theory is:

  • Codex opened or scanned the project workspace.
  • The app encountered a very large embedded HTML file, likely containing inline data/assets.
  • Because HTML is text-like and previewable/searchable, Codex/Electron may have tried to read, index, render, search, summarize, or otherwise process the full file.
  • Sampling the hot Codex process showed Electron/V8/Node activity around stream reads, JS callbacks, buffer allocation, memory copying, and GC-like work.
  • MCP/tool child processes were duplicated during this period, but they were mostly low CPU individually. The direct CPU burner was the main Codex Electron process.

So I would reframe this as a possible Electron/workspace-ingestion bug involving very large text-like artifacts. MCP fanout may make the failure worse, but it may not be the original cause in this repro.

Suggested product-side guardrails:

  • Do not eagerly ingest, render, index, or summarize very large HTML/text artifacts from a workspace.
  • Add a size cap for previewable/text-like project files.
  • Treat huge embedded HTML files like binary artifacts unless the user explicitly opens them.
  • Avoid repeatedly respawning duplicate MCP/tool child processes while the main app is already processing a large workspace artifact.

This seems especially important for creative/game projects where generated HTML boards, embedded asset boards, image references, and exported visual docs can easily become hundreds of MB.

Hylouis233 · 10 days ago

Additional sanitized macOS evidence matching this issue's MCP-child lifecycle failure.

Environment

  • macOS 26.6 (25G5052e), Apple Silicon Mac16,11, 24 GiB RAM
  • ChatGPT/Codex desktop build 26.707.31428
  • 41 local MCP definitions in the global Codex config; 40 enabled

Observed process tree after reboot

Within about 29 minutes of app/session recovery:

  • ChatGPT root: 501 descendants, about 18.50 GiB descendant RSS
  • Codex app-server worker: 320 direct children, 492 descendants, about 16.86 GiB descendant RSS
  • System total: 1,157 processes, 351 Node processes, about 23/24 GiB memory used, 11 GiB compressed, and 3.84 GiB swap used

The earlier Jetsam snapshot recorded four active codex workers. Their ChatGPT/Codex coalition contained 1,201 active processes and about 48.89 GiB aggregate footprint.

A normal AppleScript quit request returned successfully but the ChatGPT root and Codex worker remained alive. I then captured the exact ChatGPT tree and terminated only those 502 members, rechecking process name and start time before any forced signal. Without rebooting:

total processes: 1,157 -> 558
Node processes: 351 -> 0
used memory: about 23 GiB -> 7.9 GiB
compressed memory: about 11 GiB -> 507 MiB
swap used: 3.84 GiB -> about 633 MiB

This machine also experienced a watchdog kernel panic after compressor segments reached 100% with 69 swapfiles. Another agent application contributed additional MCP trees, but the captured ChatGPT/Codex tree and the targeted before/after cleanup isolate a substantial Codex-side contribution.

The behavior appears proportional to active/restored workers multiplied by the global local-MCP set. Please consider process-group ownership/cleanup for each worker, parent-death signaling for MCP children, startup reconciliation of stale workers, and a regression test that verifies all MCP descendants exit after a task/app closes.

No username, hostname, IP address, absolute path, command argument, environment variable, credential, MCP server name, conversation content, session ID, UUID, PID, or raw core data is included.

morluto · 10 days ago

A deterministic child-reaping mechanism was found in Codex CLI 0.144.1 and is still present in the current source. It is now tracked in #32219.

ProcessHandle::terminate() kills the child and then aborts wait_handle, while that wait task owns the child and is the only code that calls wait. This affects the PTY/pipe exec helper and is separate from the stdio MCP shutdown path addressed by #19753.

ofekron · 43 minutes ago

The deterministic reaping problem needs an owned process-tree lifecycle, not just best-effort cleanup at task end.

I would model every spawned Codex/MCP process with: owning task/session id, process group, child pids, startup receipt, heartbeat, terminal reason, reap attempt, and reap receipt. The critical invariant is that completion of the visible task must not be considered final until owned subprocess cleanup has either succeeded or produced a visible cleanup-failed state with enough ids for repair.

I maintain Better Agent (https://github.com/ofekron/better-agent), where detached runners and helper processes are tracked as durable owner state for crash/restart recovery. If useful, a star helps other desktop-agent builders find it.