MCP servers eat up memory when multi-tasking in Codex App

Open 💬 10 comments Opened Feb 10, 2026 by yahelroro0196
💡 Likely answer: A maintainer (guidedways, contributor) responded on this thread — see the highlighted reply below.

What version of the Codex App are you using (From “About Codex” dialog)?

260208.1016

What subscription do you have?

Business

What issue are you seeing?

Hey,
I'm working 100% of the time using the Codex app, working on multiple parallel worktrees overtime multiple days, can get to more than 10 active threads at a time.
I also have 17 MCP servers configured for my app.

Combine these two and you get about 30gb of memory used when the codex app is open on my PC, simply because i have 10 codexes working in parallel, not to mention sub agents and such.
Further more, when I close off/archive conversations in the codex app, it doesn't seem to kill the conversation process as well as MCP server processes, effectively flooding the PC with stale instances that fill up memory overtime.

A few solution I can propose to address this:

  1. if usage is through the app, share MCP connections across all codex instances. I understand it's very different from how Codex functions today but it's one approach
  2. Implement tool search so context doesn't bloat + MCP servers are connected at boot up so only the needed MCPs are used with how the conversation progress
  3. Allow turning off, turning on and restarting MCP servers incase some connections aren't working and the process is stale on the PC

What steps can reproduce the bug?

Work on multiple codex conversations in parallel

What is the expected behavior?

High memory usage on MAC

Additional information

I am using the MacBook Pro M4 Max with 36GB of Ram, so it took a lot of Codex session to reach the limit of my PC, but eventually it did.

View original on GitHub ↗

10 Comments

guidedways contributor · 5 months ago

With that many MCP servers, I'm surprised codex is even functioning. MCP servers can take up quite a lot of your context window (due to their instructions / meta-data / schema etc). Honestly, in my experience codex is just phenomenal without external assistance. It should not need that many MCP servers unless all of these are custom MCP servers that control gadgets around your house.

yahelroro0196 · 5 months ago

@guidedways it works very well. compaction handles context fill up really smoothly in the transition, and i do need some of the MCPs each time, anything i don't need i remove.
when i'm doing research i need to connect a lot of MCPs to gather context.

francoisrob · 5 months ago

I've also experienced that codex does not properly clean up the mcp servers it started up when running. That includes mcp servers started up by the sub-agents as well

yahelroro0196 · 5 months ago

bump? :)

ginishuh · 5 months ago

Related issues that may help narrow root cause and mitigation:

  • #12333: Codex CLI multi-agent spawns duplicate Serena/stdIO MCP instances instead of sharing one connection.
  • #12335: Feature request for automatic lifecycle cleanup (auto-close completed/idle sub-agents + teardown unused MCP clients).

These are CLI-focused, but the underlying lifecycle pattern seems aligned with what this App issue reports (stale MCP/agent processes accumulating over time).

If useful, we can consolidate around:
1) MCP connection sharing/pooling strategy,
2) explicit auto-close lifecycle policy for completed/idle agents,
3) deterministic MCP teardown when no active agent references remain.

ribber · 4 months ago

I encountered the same problem.

RemarkRemedy · 4 months ago

I dug into this locally and can confirm a more specific app-side failure mode that matches the original report.

My setup:

  • Codex App 26.311.21342 (build 993)
  • macOS Darwin 25.3.0 arm64 arm
  • stdio MCP servers configured for omx_state, omx_memory, omx_code_intel, omx_trace, and a custom claude_plan_review

What I observed:

  • The long-lived parent is Codex.app/Contents/Resources/codex app-server
  • Each loaded conversation spawns a fresh MCP bundle under that app-server
  • After the conversation ends or is no longer viewed, those MCP children remain alive with the same live PPID as the app-server
  • They are not orphaned to launchd; they stay attached to the still-running app-server and accumulate over time

A representative process snapshot looked like this:

63145 /Applications/Codex.app/Contents/Resources/codex app-server --analytics-default-enabled
├── 10483 node .../oh-my-codex/dist/mcp/trace-server.js
├── 10484 node .../oh-my-codex/dist/mcp/state-server.js
├── 10488 node .../oh-my-codex/dist/mcp/memory-server.js
├── 10489 node .../oh-my-codex/dist/mcp/code-intel-server.js
├── 10481 node .../claude-plan-review-mcp.mjs
├── 10618 node .../oh-my-codex/dist/mcp/trace-server.js
├── 10624 node .../oh-my-codex/dist/mcp/state-server.js
├── 10625 node .../oh-my-codex/dist/mcp/memory-server.js
├── 10626 node .../oh-my-codex/dist/mcp/code-intel-server.js
└── 10621 node .../claude-plan-review-mcp.mjs

Source-level findings from openai/codex:

  • MCP servers are created per thread / conversation, not globally. ThreadManager::spawn_thread_with_source() goes through Codex::spawn(), which creates a fresh McpConnectionManager for that session.
  • The rmcp client already has proper child cleanup if the manager is actually dropped: stdio clients use kill_on_drop(true) and a process-group guard that terminates / kills the process group on drop.
  • The app-server's intended unload path is thread/unsubscribe. If the last subscriber unsubscribes, it shuts down the thread and emits thread/closed.
  • However, on connection removal the app-server currently retains zero-subscriber threads instead of unloading them. The relevant log message in the current source is:

retaining thread listener after connection disconnect left zero subscribers

  • The core session shutdown path also does not explicitly tear down the MCP manager.

Important control experiment:

  • I launched the same Node MCP servers manually with stdin connected to /dev/null
  • Both the oh-my-codex server and the custom plan-review server exited normally
  • So this does not look like an MCP server bug first; it looks like Codex is keeping the thread and its stdio pipes alive

My current read is:

  1. the desktop app is not unloading old conversations reliably
  2. because those thread objects stay loaded in the app-server, their per-conversation McpConnectionManagers never drop
  3. therefore the MCP child processes stay alive indefinitely

This seems closely related to #12491, but the narrower root cause here is conversation / thread unload lifecycle in the desktop app, especially around thread/unsubscribe and disconnect handling.

dorukardahan · 4 months ago

Same pattern here, plus residue after Cmd+Q

@yahelroro0196 this matches what I saw. @francoisrob on sub-agent cleanup and @RemarkRemedy on the app-server unload path also line up with my side.

My setup

  • Codex.app 26.311.21342 (build 993)
  • macOS Darwin 25.3.0, Apple Silicon
  • about 18 conversations over ~2.5 days
  • MCPs: context7, playwright, chrome-devtools, webflow mcp-remote, custom Python MCP

Before I quit the app

Node: 100
Python (B12): 22
Total visible MCP-side load: ~124 processes

It looked like each conversation got its own full MCP bundle, and finished/archived conversations were not going back to a sane baseline.

I want to be clear on the same distinction people made in #12333 and #14233: i am not asking for one shared MCP instance for all active conversations. The bug on my machine was teardown after the work was already done.

After Cmd+Q

I quit Codex from the Dock on March 17, 2026. Most of the pile died right away:

Node: 100 -> 10
Python (B12): 22 -> 4
About 90 Codex child processes died on quit

But cleanup was still not complete. I still had:

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

The old survivors matched earlier Codex sessions from March 9, 2026 and March 14, 2026.

So for me this is not only "memory gets high while Codex is open". It is also "a small orphan set can survive the quit and still be there across later sessions".

Same-machine control:

  • Claude Code via Superset.app had a normal small active MCP set
  • the buildup and leftovers were from Codex.app

#10710 is why this feels like an app lifecycle gap more than a raw stdio cleanup bug. The process-group cleanup seems to exist when drop happens. The desktop app path still looks like it sometimes never reaches that clean drop for every conversation.

Keesan12 · 1 month ago

One architecture split that has helped us on a similar runtime: treat MCP child lifetime as a separate admission problem from thread lifetime.

Concretely:

  • keep per-server ownership metadata (owner_threads, last tool call timestamp, rss, idle_since)
  • tear down on refcount == 0 plus a short idle TTL, instead of tying the child to whether the parent app-server is still alive
  • expose a tiny operator receipt for every surviving helper: why it is still admitted, who still owns it, and what event will release it

That tends to solve two different pain points at once:

  1. real leaks become obvious because the helper has no live owner / no recent activity
  2. legitimate long-lived helpers stop looking like leaks because the runtime can explain why they are still around

If the team does not want global MCP sharing, a narrower fix would still help a lot: deterministic teardown on zero live thread refs, plus a visible why_alive receipt in the app so operators can tell stale from intentional.

We have been hitting the same class of failure in MartinLoop, and the biggest win was making teardown observable before trying to make it clever.

iddictive · 2 days ago

Reproduced this on the current Codex Desktop build on Apple Silicon.

A secret-safe process inventory found roughly 900 MCP-related Node processes consuming about 10 GB RSS. The repeated groups were proportional to retained task runtimes (approximately 75 copies of each configured stdio server or wrapper). Many processes had been alive for about 20 hours. Migrating compatible remote servers from stdio bridges to native Streamable HTTP prevented new bridge copies, but existing task-owned processes remained alive until Codex was restarted.

This suggests the missing layer is Codex-side lifecycle ownership, not just individual MCP implementation cleanup.

Possible fixes:

  1. Drop and reap every task-owned stdio MCP process group when a task is archived, closed, cancelled, or otherwise becomes inactive.
  2. Pool stateless/shareable MCP clients at the app-server or project level instead of spawning a full stack per retained task.
  3. Keep stateful/browser/account-scoped MCPs session-scoped, with an explicit idle timeout and cleanup contract.
  4. Start MCP servers on first tool use rather than eagerly for every task.
  5. Add a per-server process/RSS budget and surface ownership in the MCP UI: server, transport, owning tasks, process count, RSS, and a safe “restart idle instances” action.
  6. Prefer native Streamable HTTP when a provider supports it; warn when a stdio mcp-remote-style bridge duplicates a transport Codex can already speak directly.
  7. Add a regression test that opens and retires multiple tasks, then verifies bounded process count and complete child-process cleanup after the lifecycle boundary.

Account identity and state isolation must remain part of pooling eligibility: two separately authenticated MCP registrations must not be merged merely because they use the same implementation.