Codex Desktop: opening a large thread causes sustained 100%+ CPU until app restart
Summary
In Codex Desktop, opening a large existing conversation/thread can cause the Codex main process to stay at 100%+ CPU. Switching away to another conversation does not recover CPU usage; only fully restarting Codex clears it.
Version
- Codex Desktop version:
26.527.31326 (3390) - Platform: macOS
Reproduction
- Start Codex Desktop.
- Open a new/empty conversation: CPU remains normal.
- Open an existing large conversation/thread.
- Observe Codex main process CPU spikes to roughly
100%-200%. - Switch to another conversation, including smaller conversations.
- CPU remains high until Codex Desktop is fully restarted.
Observed Details
The problematic thread file is around 501 MB and contains many saved tool results, not just regular chat text. Local inspection showed the size was mostly from persisted tool output records:
function_call_output: about228 MBmcp_tool_call_end: about171 MB- assistant/user messages are much smaller by comparison
Cleaning the local logs database did not fix the issue. The active thread itself was small in another test case, but opening this large thread first made the whole app remain slow until restart.
Sampling the Codex Desktop main process while CPU was high showed activity concentrated around Electron/Node IPC paths, including libuv stream -> JS callback -> Buffer/memmove style work. The renderer also showed V8/renderer work during the spike.
Expected Behavior
Opening a large thread should not permanently peg CPU. If the large thread is expensive to load, Codex should either:
- lazily load/virtualize thread history,
- truncate or summarize persisted tool outputs in the UI,
- avoid loading full tool output payloads unless expanded,
- cancel/release parsing and rendering work when switching away from the thread,
- and release memory/CPU pressure after navigating to another conversation.
Actual Behavior
After the large thread is opened once, CPU remains high even after switching away. A full app restart is required to recover.
Impact
Large long-running agent threads with many tool calls become effectively unsafe to open in Codex Desktop, because one open can make the whole app sluggish until restart.
9 Comments
Potential duplicates detected. Please review them and close your issue if it is a duplicate.
Powered by Codex Action
Probably related too: https://github.com/openai/codex/issues/25243
I’m seeing what looks like the same class of issue on macOS, with a stronger native app-server memory signal.
Repro shape:
vmmap/sample, then may partially fall back down.Captured evidence from one reproduction:
/Applications/Codex.app/Contents/Resources/codex app-server --analytics-default-enabledsamplecaptured while hot:Physical footprint: 13.2GPhysical footprint (peak): 13.2Gvmmap -summarycaptured during the same spike:Physical footprint: 10.2GPhysical footprint (peak): 13.0GDefaultMallocZone:10.5GallocatedMALLOC_MEDIUM:9.6G10.2G swappedsamplestacks are mostly stripped, but show heavy native heap/copy/path work such as_platform_memmove,_realloc,szone_malloc_should_clear,read,realpath,stat/lstat, and somefork.This seems consistent with Desktop eagerly hydrating/copying a large local thread/tool-output history into native app-server memory when the thread is opened. I’m avoiding thread names/details here, but the affected local history is a long-running tool-heavy Desktop thread.
This may also relate to #21134, but this repro is specifically “opening/selecting a large existing thread causes the native app-server to spike to ~13GB physical footprint.”
I can reproduce a similar large-history CPU/memory case on Codex Desktop.
Observed behavior:
codex app-serverand not an external child command.Local history shape:
response_itemandevent_msgrecords.type: "compacted"records.compactedevents with largepayload.replacement_historyarrays.This suggests the issue is not only large persisted tool outputs. Very large
compacted/replacement_historypayloads also seem to be enough to make the desktop renderer do expensive hydration/parsing/rendering work. Once triggered, the UI/GPU side appears to remain hot even though tool subprocesses are idle.I reproduced this same failure class on a substantially newer desktop build, with a much smaller thread and a captured main-process stack.
Environment:
26.803.41515(build6321)15.2(24C101), Apple Silicon (Mac16,7, arm64)Trigger and timing:
task_completeat14:54:03.905local time.100%CPU by the next sample at14:54:05and remained at approximately99-100%until force-quit at14:58:14.The affected rollout JSONL was only
19,106,230bytes with4,180records. The final turn reported210,304input tokens against a258,400context window. A previous matching hang involved another long-running task whose rollout JSONL was18,834,563bytes, suggesting the unsafe threshold can be far below the 501 MB case originally reported here.A five-second live sample captured during the hang showed all 230 samples on the Electron main thread in the same Node/V8 callback path. The symbolized stack passes through:
The renderer and app-server children were not the CPU source. System memory remained healthy (58% free), the ChatGPT main process RSS was approximately 382 MB, and no
chrome-headless-shellprocess was active. This appears consistent with pathological main-process serialization/IPC or task-state persistence immediately after turn completion, rather than model execution, browser automation, or memory pressure.I have not attached the raw task transcript or process listing because they contain private project context/helper arguments. I can provide the sanitized
sample, macOS.hang,cpu_resource.diag, and a timing/metrics summary if maintainers indicate the preferred channel.Follow-up: I found a concrete trigger for the Aug 11 recurrence. This one was not caused solely by opening a large thread; a malformed persisted heartbeat automation entered an unbounded resume loop and eventually pegged the Electron main process.
Environment
Malformed persisted automation
A stale heartbeat created for a PR watcher was still
ACTIVEeven though the PR had already merged. Its persisted record contained:The literal value
"thread"is not a valid thread UUID.When the heartbeat fired, the desktop log repeated this sequence approximately every 0.78 seconds:
There was no terminal stop or meaningful backoff. On Aug 11, the log recorded 1,142
heartbeat_automation_resume_failedevents from 12:58:02 PM through 1:12:59 PM ET.CPU/hang correlation
ChatGPT.cpu_resourcediagnostic: 90 seconds of CPU over 121 seconds, exceeding the 50% limit.node::InternalCallbackScope::Close() -> v8::ValueSerializer::WriteValue(...) -> optimized/JIT JS frames.The invalid automation survived an app restart and immediately resumed the same retry loop. The new launch accumulated another 426 failures before cleanup.
Recovery confirmation
I deleted that exact obsolete heartbeat through the app’s automation API. Its persisted file was removed, and the failure count stayed fixed at 426 during verification instead of increasing every ~0.78 seconds. Main-process CPU returned to normal.
This suggests at least one path to the sustained-main-process-CPU failure is repeated serialization/IPC/error-state work caused by a malformed heartbeat target, not only oversized conversation hydration.
Potential safeguards:
target_thread_idas a real thread UUID when creating, updating, and loading heartbeat records.-32600 invalid thread/session idas non-retryable.I can provide the sanitized desktop log, CPU resource diagnostic, live
sample, and monitor timeline if maintainers indicate the preferred channel.A 501 MB thread dominated by ~228 MB of
function_call_outputand ~171 MB of MCP results is a useful persisted-tool-output case, especially because opening it leaves the app pegged until restart.I’m testing whether Codex Rescue diagnoses this safely with bounded reads. It cannot fix Desktop’s IPC/CPU loop or automatically truncate the original history. It is local-first, keeps the source rollout untouched, does not replay calls, and verification remains fail-closed.
If the affected session still exists, could you run:
pipx install codex-rescue==0.1.0a3codex-rescue sessionscodex-rescue doctor --latestPlease share sanitized diagnostic output only—no raw rollout, MCP/tool contents, databases, prompts, repository data, paths, or secrets.
https://github.com/shleder/codex-rescue
Adding a current macOS reproduction on a substantially newer Desktop build. This case is notable because the affected rollout is much smaller than the original 501 MB report, and the Desktop logs show a clear split between fast app-server operations and a long renderer/UI stall.
Environment
26.818.61809(build7019)0.149.0-alpha.4.326.5.2(25F84), Apple Silicon (arm64)Privacy-safe rollout metadata
90,766,549bytes (86.56 MiB)9,2435,363 response_item,3,730 event_msg, and17 compacted4,752,249,4,716,958,4,244,607,4,240,702, and3,751,172bytesReproduction and timing
Sanitized Desktop timing from the affected selection:
02:09:29.894thread/read:4 msthread/resume:374 msmaybe_resume_success:02:09:30.404thread/turns/listresponses:250 msand248 msResizeObserver loop completed with undelivered notifications:02:09:30.44702:10:13.901This run does not look disk- or app-server-bound: the read, resume, and turn-list operations all completed in under 400 ms, but the renderer/UI was effectively unavailable for about 43 seconds afterward. Repeated
ResizeObserver loop completed with undelivered notificationserrors continued around later interaction with the task.Ambient suggestions as an amplifier, not the initial trigger
The first stall began before any ambient suggestion generation, so background model inference cannot explain the initial freeze.
However, reopening the same task later activated it at
02:10:41.909, followed by anambient_suggestionsephemeral generation at02:10:42.348. That run used:91,56862,720gpt-5.6-terra, medium reasoning29,273 ms0A separate home/projectless ambient generation in the same period used
242,577input tokens (182,016cached), ran for63,056 ms, and also returned zero suggestions. This appears to be an additional resource/context-assembly amplifier when revisiting a heavy project, but not the root trigger of the first UI stall. This overlaps with the background-prefetch failure family reported in #35799.Assessment
This supports treating initial thread loading as a bounded renderer contract rather than only optimizing raw JSONL parsing or app-server reads. A rollout below 100 MiB, with several multi-megabyte records, is already sufficient to make the current macOS Desktop UI appear frozen even though the backend read/resume calls finish quickly.
Useful safeguards would include:
thread/turns/listcompletes.For threads experiencing persistent 100%+ CPU utilization caused by oversized
function_call_outputand heavy persisted MCP payloads:The CPU pegging occurs because the renderer and parser attempt unbounded parsing of large base64/JSON strings during session load.
If you need to diagnose or extract the thread without locking the desktop app, Vetto provides bounded session diagnosis and size-capped observation:
When running future long-running tasks, wrapping with
vetto --tui=statusline --agent codex -- codex exec ...enforces bounded memory ringbuffers (5 FPS rendering cap, strict memory limits), preventing bloated tool outputs from freezing host desktop processes.