Skip to content

[Linux Desktop] Ambient suggestions/prewarm leak MCP and node_repl stacks every ~5 minutes #44525

Closed

Description

@OwnDing

Summary

On the official ChatGPT/Codex Linux desktop package, leaving the app running for several days causes unbounded memory growth because periodic desktop-owned thread/start / startup_prewarm sessions create new node_repl and bundled MCP helper process stacks that are not reaped.

In this incident, the single long-lived Codex app-server accumulated 1,082 helper processes and approximately 12.65 GiB PSS plus 5.19 GiB swap. The system reached 18/22 GiB RAM and 8/8 GiB swap. Fully terminating the Codex scopes immediately returned the machine to about 6–7 GiB used RAM.

This looks like a Linux reproduction/variant of #12491, #25015, #30408, and the ephemeral-session lifecycle described in #39783. The apparent trigger here is periodic ambient suggestions/startup prewarming rather than user-created subagents.

Environment

  • ChatGPT/Codex Desktop RPM: chatgpt-26.901.41123-1.aarch64
  • Bundled Codex CLI: codex-cli 0.153.3
  • OS: Oracle Linux Server 10.2, aarch64
  • Kernel: Linux 6.12.0-205.92.4.2.el10uek.aarch64 aarch64
  • Physical memory: approximately 22 GiB
  • Swap: 8 GiB
  • Desktop: GNOME Wayland headless session
  • Subscription: not included; this appears independent of plan/account tier

Relevant configuration before mitigation:

[desktop]
ambient-suggestions-enabled = true

[features]
js_repl = false

[mcp_servers.node_repl]
command = "/usr/lib/chatgpt/resources/cua_node/bin/node_repl"
startup_timeout_sec = 120

The bundled Browser, Chrome, codex-app-tools, and unified-computer-use plugins were enabled.

What issue are you seeing?

The desktop app's long-lived app-server repeatedly starts fresh local MCP/helper stacks and retains the old ones indefinitely.

At capture time:

  • 537 processes with comm MainThread
  • 538 processes with comm node_repl
  • 1,082 processes in the ChatGPT/Codex app-server tree
  • app-server tree PSS: 12,652.8 MiB
  • app-server tree private memory: 12,508.4 MiB
  • app-server tree swap: 5,190.7 MiB
  • systemd scope memory.current: 13,147,430,912 bytes
  • systemd scope memory.swap.current: 5,010,042,880 bytes
  • systemd scope task count: 8,643

The app-server itself had grown to approximately 3,289.6 MiB PSS and 547.3 MiB swap.

The retained helper groups had this shape:

ChatGPT
└─ codex ... app-server
   ├─ node ./server.mjs
   ├─ node .../unified-computer-use/.../scripts/launch.mjs
   │  └─ node_repl
   └─ node_repl

Most helpers were direct children of the same app-server. New groups appeared at roughly five-minute intervals. During sustained reproduction, each hour added approximately 22–24 MainThread processes and 22–24 node_repl processes.

The local logs_2.sqlite correlated each creation cluster with a new desktop request and a new ephemeral thread ID:

app-server request: thread/start
thread_start.create_thread
thread_spawn
session_init
startup_prewarm
mcp.runtime.refresh:new{server_name=cua_repl}
mcp.runtime.refresh:new{server_name=node_repl}
mcp.runtime.refresh:new{server_name=codex_app}
mcp.runtime.refresh:new{server_name=codex_apps}

No user turn or MCP tool call was required for the periodic prewarm sessions.

A normal SIGTERM to the main ChatGPT PID closed the main UI process, but the codex app-server and hundreds of helpers remained alive after being reparented to the user systemd process. Terminating the two Codex-owned systemd scopes was required to remove all retained processes.

This was not GNOME or filesystem cache pressure: the user GNOME Shell was approximately 237 MiB PSS at diagnosis, while terminating Codex reduced used RAM from about 18 GiB to 5.9 GiB immediately.

Steps to reproduce

  1. Install and launch the Linux ChatGPT/Codex desktop package.
  2. Enable the bundled Browser/Computer Use plugins and leave ambient suggestions enabled.
  3. Keep the app and an existing project open for an extended period. User activity does not appear necessary once the periodic behavior starts.
  4. Monitor helper counts:
watch -n 30 'pgrep -xc MainThread; pgrep -xc node_repl; free -h'
  1. Inspect ~/.codex/logs_2.sqlite and correlate process birth times with thread/start, startup_prewarm, and mcp.runtime.refresh records.
  2. Observe a new helper stack approximately every five minutes and no return to baseline.
  3. Leave the application running for one or more days; RAM and swap eventually become exhausted.
  4. Fully terminate the ChatGPT/Codex app-server scope; memory returns immediately.

Mitigation and A/B result

The following was changed:

[desktop]
ambient-suggestions-enabled = false

After fully terminating all old Codex scopes and relaunching the desktop app, I monitored it for six minutes, covering the previous five-minute creation interval:

codex=1  MainThread=0  node_repl=0
used RAM remained approximately 6.96–7.02 GiB
available RAM remained approximately 15.39–15.45 GiB

No new helper stack appeared during that interval. This strongly associates the periodic trigger with ambient suggestions/prewarming, while the unbounded impact is caused by missing teardown of the inherited MCP runtime.

Expected behavior

  • Desktop-owned ambient-suggestion/prewarm threads that do not need tools should start with MCP servers disabled.
  • Completion or abandonment of an ephemeral/prewarm thread should shut down its session-owned MCP runtime and child process group.
  • Periodic background work must return process count and memory to a bounded baseline.
  • Fully quitting ChatGPT Desktop should terminate the app-server and all helper descendants.
  • A long-running idle desktop session should not require periodic manual restarts to avoid system-wide memory and swap exhaustion.

Additional information

Raw SQLite/session files are not attached because they contain prompts, local paths, and private project metadata. Sanitized counts, timestamps, process relationships, and log signatures are included above. I can provide a more targeted diagnostic if maintainers specify the required fields.

Activity

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

    @johnpippett

    Additional Linux evidence: system memory exhaustion on Desktop 26.908.40834

    This report adds a systemd out-of-memory event and local process measurements. The results are consistent with this issue, but they do not prove the same internal cause.

    Environment

    • ChatGPT/Codex Desktop package: openai-codex-desktop 26.908.40834-1. Version checked from the installed package, not the About dialog.
    • Bundled backend: codex-cli 0.154.0-alpha.6.2.
    • Omarchy 4.0.3-1, Arch Linux, x86-64.
    • Kernel: 7.2.3-arch1-3; Hyprland: 0.56.2-2, Wayland.
    • Approximately 30.7 GiB usable RAM and 61.4 GiB configured swap. Swap includes both zram and a disk swap file.

    User observations

    I often leave Codex open for long periods. After I disconnected an external monitor and opened the laptop, the whole desktop was very slow. I closed and opened the lid again. The system later recovered. Codex also stopped at its startup logo during this incident. A later app restart restored its interface.

    This was not a controlled reproduction. The monitor and lid actions are context, not established causes.

    Confirmed local evidence

    The system journal records the following events on 2026-09-14. Times are local, UTC-07:00.

    Time Event
    09:56:54 Suspend ended.
    09:57:27 The saved Codex log recorded a Desktop thread/start request with turn_trigger: Some("ambient_suggestions").
    09:57:30 Codex recorded startup_prewarm for that thread.
    09:57:47 The kernel reported a page-allocation failure in the Wi-Fi driver.
    09:57:59 systemd-oomd selected the application scope for termination. System RAM and swap use exceeded its 90% threshold.

    The selected scope was named app-org.chromium.Chromium-2975.scope. Its recorded peaks were 25.6 GiB memory and 51.7 GiB swap. These are separate peak values, not a simultaneous total. systemd reported:

    systemd-oomd killed 3333 process(es) in this unit.
    Failed with result 'oom-kill'.
    

    The scope name is important: the restarted ChatGPT executable also runs in an app-org.chromium.Chromium-<PID>.scope. This label does not identify a separate browser session.

    About 84 minutes after the restart, the new scope contained 135 processes and used approximately 6.9 GiB according to memory.current. Process inspection found 23 node_repl processes and 11 copies each of several different helper servers. This is a snapshot, not a measured growth rate. It does not establish that each helper was unused.

    Mitigation and limits

    We backed up ~/.codex/config.toml and added:

    [desktop]
    ambient-suggestions-enabled = false

    The configuration parses correctly. No other settings changed. At the time of this report, we have not completed a restart and observation period after this change. We cannot yet report that the mitigation works here.

    There is no process-by-process memory capture from before the old scope was terminated. The ambient task may have added load to an already overloaded app. The exact source of the earlier accumulation remains unconfirmed. Slow resume and separate Chromium crash notifications also need further diagnosis.

    Expected behavior and question

    Background tasks should release helper processes when those processes are no longer needed. Leaving the app open should not exhaust system memory.

    Is there a fix planned for a desktop release, or a supported mitigation beyond disabling ambient suggestions and fully restarting the app?

    Private paths, prompts, thread identifiers, and raw databases are omitted.

    This comment was prepared with the assistance of AI. Local measurements and limits are stated above.

  3. stephenphilipjensen-ship-it commented on Sep 18, 2026

    @stephenphilipjensen-ship-it

    Same leak reproduced on Windows. It is not Linux-specific.

    Environment: Windows 11 Pro (build 26200), Codex Desktop client 26.911.61220 (MSIX OpenAI.Codex_26.915.3509.0), ChatGPT.exe 153.0.8010.48, codex-command-runner 0.155.0-alpha.9, 32 GB RAM.

    Observed after 7.7 h uptime (one app-server, codex.exe child of ChatGPT.exe):

    • 105 node_repl.exe, 118 bundled cua_node node.exe (cua-repl.mjs and codex_app tools server.mjs via cmd.exe), 104 system node.exe for plugin MCPs (./mcp/server.mjs from openai-developers, ./mcp/server.bundle.mjs from creative-production), and 12 codex.exe under node_repl. In total about 10 GB working set.
    • The processes come in sets of about 50, one set per thread the app-server has loaded. None of them are reaped when a thread goes idle. Commit charge reached 40.5 of 47.4 GB, and the whole machine became sluggish.

    Amplifier: Editing config.toml (disabling two plugins) caused config/batchWrite, then 39 thread/start, 8 thread/resume and 19 model/list within one minute. About 150 new MCP processes were spawned. Killing idle MCP processes of loaded threads just gets them respawned via mcp.runtime.refresh_if_dirty.

    Also in logs_2.sqlite: Repeated MCP startup timeouts (node_repl handshake 120 s, cua_repl 100 s, codex_app 10 s, plugin servers 30 s) add latency to new turns. A remote plugin that never completed OAuth (cloudflare-api) was retried 66 times in 10 days.

    Workaround: Disable unused plugins. Restart the app-server when idle, because the desktop app respawns it and the UI survives. Run an hourly job that kills MCP helpers older than 3 h with no CPU use.

    Expected: Helpers are shared across threads or shut down when a thread or ephemeral session is unloaded. A catalog refresh should not spawn per-thread stacks. A plugin that failed OAuth should not be retried on every thread start.

  4. 84dnnvbdvp-debug commented on Sep 18, 2026

    @84dnnvbdvp-debug

    There is a fairly sharp lifecycle discriminator in the exact backend version from the new Windows report. rust-v0.155.0-alpha.9 resolves to 434535bddfaf405a032f57be3c1096dd25ff6312. In that release:

    • SessionServices::mcp_runtime is documented as “the single owner of live MCP connections for this thread”, so MCP connection ownership is deliberately per-thread.
    • The app-server thread_unsubscribe regression explicitly asserts that a subscribed, idle thread stays loaded even with thread_unload_delay_secs = 0. The unload countdown begins only after the last subscriber is gone.
    • The normal session shutdown path explicitly calls mcp_runtime.shutdown().

    So “thread is idle” is not the backend teardown boundary; “thread is idle and has no subscribers” is. That suggests a narrower discriminator for the observed ~one helper set per loaded thread: if Desktop keeps a subscription on every thread it has loaded, the backend will intentionally retain each thread’s MCP runtime indefinitely. That would fit the reported per-loaded-thread scaling without requiring a leak inside McpConnectionSet itself. This does not prove Desktop is retaining those subscriptions; that client state is the remaining branch to test.

    A deterministic regression could make that distinction cheaply:

    1. Start N threads with the same stdio MCP configuration and let them become idle; verify the subscribed baseline retains N runtimes.
    2. thread/unsubscribe N-1 of them and wait past the unload delay; require thread/closed, thread/loaded/list to drop them, and mcp.connections.live / child-process count to fall accordingly.
    3. Perform the configuration refresh from the report and require those unloaded threads to stay unloaded and their helpers not to respawn.

    If step 2 tears the helpers down correctly on the reported backend, the high-value fix boundary is likely Desktop subscription/liveness policy (or decoupling event subscription from full MCP-runtime retention), not global MCP sharing. If the helpers survive thread/closed, then the teardown path itself is leaking despite mcp_runtime.shutdown().

  5. 0xdevalias commented on Sep 22, 2026

    @0xdevalias

    Related but distinct observation on macOS Desktop 26.903.71938 / Codex 0.153.4: I found 20 loaded sessions with ephemeral=true, source mcp_extension_host, no turns/path/parent, cwd /, and no state-DB, live-window DOM, or spawn-edge presence. Stopping the 17-day-old Chrome extension native host did not unload them after >60 s, so the visible native host was not their current owner; an internal Desktop/app-server owner retained them.

    Separately, logs showed a focus-triggered bundled-plugin reconciliation restart MCP fleets across the already-retained session set in a tight burst. That explains the apparent mass respawn, but not the original owner leak. I have not tested this issue's ambient-suggestions mitigation, so I would not generalize that workaround to this cohort. A final-owner thread/unsubscribe can unload a task after ~60 s (confirmed once), but these internally owned ephemeral sessions require subscriber diagnostics or explicit unload support.

  6. yuzhu-oai commented on Oct 2, 2026

    @yuzhu-oai
    Contributor

    Fixes have been merged and will be released soon.

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

    LinuxappIssues related to the Codex desktop appapp-serverIssues involving app server protocol or interfacesbugSomething isn't workingmcpIssues related to the use of model context protocol (MCP) serversperformance

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions