[macOS Desktop] Renderer memory growth, repeated reconnects and lost steered messages
Nobody has claimed this yet.
- Dominant language
- Rust
- Stars
- 125k
- Forks
- 19.4k
- PR merge metrics
- PR metrics pending
Description
Codex Desktop became unusable during long tasks with tools and agents. Renderer memory grew sharply, responses stalled for minutes, and accepted messages disappeared from desktop history after steering and interruption. I restarted the app to recover usability.
Environment: Desktop 26.908.40834 (build 8881, ChatGPT.app); CLI 0.154.0-alpha.6.2; Chromium 152.0.7977.83; macOS 26.5.2 (25F84), arm64. uname -mprs: Darwin 25.5.0 arm64 arm. Subscription pro 20x.
Renderer memory growth
The same renderer, PID 2340, launched at 16:17:26.290, produced these measurements:
| Time | Evidence | Memory |
|---|---|---|
| 19:39:35 | Activity Monitor screenshot | Real memory 1.18 GB |
| 19:40:17.895 | Native sample | Physical footprint 892.1M, peak 1.1G |
| 19:48:02 | Activity Monitor screenshot | Real memory 2.82 GB |
| 19:48:19.123 | Native sample | Physical footprint 2.5G, peak 2.6G |
Real memory grew 1.64 GB in 8 minutes 27 seconds. Physical footprint is a separate metric that independently corroborates the increase.
Frame activity in the earlier trace
Over 55.70 seconds, the trace records 9,131 UpdateLayoutTree events and 9,097 each of Layerize and PaintArtifactCompositor::Update. Of 9,462 LocalFrameView::ScheduleAnimation events, 9,154 are attributed to ScheduleServiceOnNextFrame. Only two RequestAnimationFrame and two FireAnimationFrame events appear. A loading shimmer span accounts for 1,879 of 3,972 paint events, despite little sampled JavaScript.
This trace predates the growth interval. In the later native sample, the main thread is waiting in 1,677 of 2,228 observations, which does not establish a sustained busy loop. Neither capture supplies heap retaining paths.
Repeated stalls, including after successful compaction
stream disconnected before completion: idle timeout waiting for websocket
| Time | Evidence |
|---|---|
| 18:28:28–18:43:40 | An earlier task crosses the 244,800-token compaction threshold, logs two idle timeout retries about five minutes apart, then I stop it. Files had been edited about 16 minutes before Stop; the change summary appeared afterward. |
| 19:38–19:49 | During renderer growth, usage is 175,782–190,381, with token_limit_reached=false. The 154 backend entries checked contain no remote compaction attempt or matching idle timeout. Some waits were interrupted before five minutes. |
| 20:44:07; 20:49:09 | Retries 1/5 and 2/5, with preceding recorded usage below the threshold. |
| 20:49:53–20:52:54.913 | Usage reaches 251,680, triggers compaction, and the rollout records its successful completion. |
| 20:59:05; 21:04:07 | Another pair of idle timeouts, after compaction, with preceding usage only 57,646 and token_limit_reached=false. |
Seven accepted submissions missing from desktop history
Five submissions during the first episode: I sent text and images while work was active. They appeared uploaded, but the assistant did not receive them, and interruption and restart left them missing from history.
Backend TurnInput records confirm receipt at 19:38:52, 19:40:11, 19:42:43, 19:44:14 and 19:48:48, including eight attachment references to four screenshots. None appears in rollout user events, user response items or indexed history. Four other submissions persist normally, providing a control for the lookup.
Successful turn/steer replies are followed within milliseconds by thread/queue/delete. Interruptions occur at 19:42:54 and 19:47:19, then backend shutdown at 19:49:21. The missing text and attachment paths were recovered from logs.
Two messages sent from mobile: These appeared in the desktop queue. Choosing to steer removed them without rendering them in desktop history. A mobile photo still shows:
- “But this pattern seems common” — backend receipt 21:06:15.243.
- “The history audit just had a reconnecting issue” — receipt 21:06:55.733.
The desktop photo shows Reconnecting 2/5. Interruption follows at 21:07:03.980. Both messages are absent from the same three local history representations by client ID and exact text. This episode logs queue listing and interruption but no matching steering or queue deletion RPCs. Mobile storage was not inspected; visible mobile copies remained.
Source findings and diagnostic tests
Draft PR diegomrsantos/codex#2 provides runnable scripts against the matching desktop build. Both scripts passed, covering 30 signature calls, a control for changed agent status and nine streaming and retention scenarios:
- Repeated scans:
notifyConversationCallbacks → Zzt → Qztrebuilds the subagent signature before metadata equality checks. Every call scans all items in histories of 100, 1,000 and 10,000, with or without subagent calls. All 24 ordinary text updates leave the result unchanged; changing agent status changes it. - Streaming amplification:
TKtnormally drains 24 characters per frame, using document visibility rather than task selection. Connecting it to the scan produces the counts below. All modes preserve the text, drain buffers and release pending frame work. Reasoning summaries drain in one frame. - Retention: Active or unfinished tasks are excluded from normal expiry after three hours. Tests retain them after four hours, then select them once completed. Capacity selection chooses the two oldest of twelve eligible tasks while excluding twenty active ones.
For 2,400 characters over 10,000 synthetic history items:
| Mode | Updates | Item visits |
|---|---|---|
| Ordinary visible streaming | 100 | 1,000,000 |
| Completion requested | 8 | 80,000 |
| Document hidden | 1 | 10,000 |
Separate durability lead: installed desktop sendNow deletes queued input after steering succeeds. Audited Rust code acknowledges input stored in an in-memory pending vector. Consumption and normal completion record it; explicit abort clears it, and graceful shutdown uses that abort path. Queue deletion does not check history persistence.
Source provenance
Renderer entry: webview/assets/app-initial-9b95fa538c62.js.
SHA-256: 737070f94a072d2b4ede9f326e3e1c4142fb82198961251c2e70479b3f926275.
Rust revision: 6b9533977a6270821c773adb879c9cb5dc0ce4bb. Locations beneath codex-rs:
app-server/src/request_processors/turn_processor.rs:1066,:1162— acknowledgement.core/src/session/turn_input.rs:637–658— pending input.core/src/tasks/mod.rs:516–536,:621–657— interruption and completion.core/src/session/handlers.rs:284–289— shutdown.
Related reports and fixes
| Reports or PRs | Relationship |
|---|---|
| #38176; trace in #33996 | Renderer growth and expensive atom and subagent membership work. The latter differs from my earlier trace with little sampled JavaScript. |
| #42896 | Compaction stalls; this report additionally records timeouts after successful compaction. |
| #44335; #45311 | Suggested duplicates: an unconsumed steer remaining visible with a copy button, and a new submission involving thread/revert then turn/start. This report adds accepted inputs absent from durable local history after interruption. |
| #45077 | Assistant messages disappearing after steering, including restoration after restart, rather than missing user submissions. |
| #38653; #32722 | Large history bootstrap and streaming snapshots across windows: related allocation leads. |
| Merged #44487; #42451 | Prompt preservation when compaction before a turn fails, and TUI steering acknowledgement. Neither establishes durability of pending desktop steering interrupted before consumption. |
| Merged #44255; #45399 | Compaction routing and backend timer cleanup; no demonstrated renderer fix for this incident. |
| #43140 | Separately reproduced HTTP SSE failure; these stalls use WebSocket. |
What remains unresolved
There is no established common cause, identified owner of the 2.5 GB footprint, or fix verified for this incident. The passing tests do not reproduce the full memory growth, stream stalls or steering loss. They characterize mechanisms with synthetic history views and an explicit connection between the buffer and scan; they do not exercise production notifications, unsubscribe I/O or steering durability, or measure runtime and retained bytes. The audited Rust revision has not been matched to the installed backend. The late change summary may also have a separate explanation.
The next useful checks are retained allocations during actual growth, scan and animation costs during stalls, and a backend regression for accepted steering interrupted before consumption. Expected behavior: tasks remain usable or report a recoverable failure, and accepted text and attachments remain recoverable across Stop and restart.
Preserved locally: two native samples, six Activity Monitor screenshots, both mobile and desktop photos, scoped logs, recovered inputs, trace summaries and source evidence. A third native sample remains uninspected. Raw incident files were not uploaded; the published diagnostics contain no private conversation data.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with the renderer entry webview/assets/app-initial-9b95fa538c62.js and the scripts in draft PR diegomrsantos/codex#2, then inspect the cited Rust paths in codex-rs. Re-run the scan, streaming, and retention tests, and investigate retained allocations and stall behavior during actual growth. Done means a reproduced cause or regression test covering accepted steering inputs and attachments across interruption and restart.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- javascript, rust
- Domain
- backend, desktop, performance
- Issue type
- Bug
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Active
- Clarity
- Needs clarification
- Newbie friendliness
- 25/100