Repository navigation
Codex Desktop high CPU from unbounded active thread metadata and local history/list processing #24510
Description
Codex Desktop app-server high CPU from unbounded active thread metadata / thread-list path
Summary
Codex Desktop can sustain high CPU/GPU usage when the local profile contains a
large number of active threads with large title / preview /
first_user_message metadata in ~/.codex/state_5.sqlite.
This appears to be a thread-list / sidebar / local-history scalability issue.
The usage pattern is high-volume, but it is within expected Codex workflows:
long local sessions, repeated agent/review/automation runs, and many project
threads.
The hottest path we observed was not model inference. It was local state
enumeration and metadata handling around active thread listing.
Environment
- Codex Desktop:
26.519.41501 - Platform: macOS
26.4.1 - Hardware: Apple Silicon
- Local state DB:
~/.codex/state_5.sqlite - Local sessions:
~/.codex/sessions/.../rollout-*.jsonl
Observed Symptoms
- Codex Desktop /
codex app-serverkeeps using CPU even when no intentional
user task is running. - Desktop renderer/GPU can also become warm depending on where the bottleneck
surfaces. - Restarting Codex may temporarily change the active PID, but the expensive
local-state path returns. - The problem correlates with large active local thread metadata and repeated
thread-list/sidebar/reconnect activity, not with a single running model call.
Local Diagnostics
Current redacted profile statistics after one manual cleanup pass:
active threads: 9,456
active metadata payload chars: 317,838,640
archived threads: 3,264
archived metadata payload chars: 328,436,582
Before that cleanup, the profile had roughly:
active threads: 12,712
active metadata payload chars: ~403 MiB
one repeated review/agent group: 3,256 rows, ~201 MiB
The slow active thread-list query shape observed locally:
SELECT ...
FROM threads
WHERE archived = 0
AND preview <> ''
AND model_provider IN (?)
AND created_at_ms < ?
ORDER BY created_at_ms DESC
LIMIT ?Query plan:
SEARCH threads USING INDEX idx_threads_archived (archived=?)
USE TEMP B-TREE FOR ORDER BY
So the query starts from archived=0, scans a very large active set, and then
sorts for the sidebar/list path. The cost is amplified because selected columns
include large text metadata used as title/preview/first-user-message.
Long-running Goal Session Evidence
One long-running goal session also showed that active local sessions can keep
the history/logging path warm for a long time.
Redacted local session:
thread id: <redacted local thread id>
created: 2026-05-25 22:26:26 KST
still active in state DB at inspection time
cwd: /Users/.../<project>
rollout JSONL size: ~38.7 MB and still growing
state DB tokens_used: 511,046,356
state DB has_user_event: 0
Rollout JSONL event counts at inspection time:
lines: 22,546
function_call: 4,879
function_call_output: 4,875
thread_goal_updated: 3,998
token_count: 3,459
reasoning: 1,894
message: 1,061
agent_message: 1,002
custom_tool_call: 388
custom_tool_call_output: 388
patch_apply_end: 374
context_compacted: 28
Tool-call distribution:
exec_command: 3,408
write_stdin: 1,271
update_plan: 196
get_goal: 3
create_goal: 1
The thread_goal_updated events were emitted very frequently:
thread_goal_updated count: 3,998
median interval: 6.148 seconds
p95 interval: 33.085 seconds
p99 interval: 54.032 seconds
largest observed gap: 5,015.697 seconds
Near the end of the sample, the rollout was still appending events such as:
write_stdin call
thread_goal_updated status=active tokens=13,127,963 seconds=34,696
function_call_output
token_count
So this was not just a large historical file. The active goal session was still
emitting progress/heartbeat-like events and polling a running command.
logs_2.sqlite also showed a short retained window for this same thread:
time window: 2026-05-26 09:24:47-09:27:22 KST
rows: 923
estimated bytes: 10,259,493
largest row: 781,177 bytes
top target: codex_client::transport|TRACE
top target bytes: 9,016,481 across 12 rows
process: codex resume <same thread id>
In a 15 second observation window, the rollout file continued to grow by 2,627
bytes. The log table appeared to be subject to retention/pruning, so row counts
can decrease while the max timestamp continues advancing.
Important confounder: this specific session also still had a real child task
running under the Codex resume process:
codex resume <same redacted thread id>
child: python scripts/<project-audit>.py --skip-fetch --force
child CPU at inspection: ~100%
cwd: /Users/.../<project>
That means this particular long-goal sample should not be described as an
entirely idle Codex session. It does show, however, that very long active goals
can keep Codex's rollout/log/session-sync surfaces hot, and it can be hard for a
user to tell from the Desktop UI which old goal/session is still driving local
work.
Why This Looks Like A Product Scalability Bug
The trigger is heavy local usage, but the usage itself is not exotic for Codex:
- users can create thousands of threads
- some threads can be very long
- agent/review/automation flows can create many repeated sessions
- long-running goals can stay active for many hours and continue writing local
progress/log events - sidebar, resume, reconnect, and archive reconciliation should have bounded
cost regardless of total local history size
The app appears to treat local conversation history more like directly coupled
UI metadata than like a large local product database. The list/sidebar path
should probably operate on bounded metadata projections and lazy-load full
history only when a specific thread is opened.
Related Issues
These seem adjacent or overlapping:
- macOS Desktop UI lag with excessive codex disk writes despite low system resource pressure #21007 macOS Desktop UI lag with excessive codex disk writes despite low system resource pressure
- Codex Desktop renderer spins at high CPU while codex MCP servers are idle #20435 Codex Desktop renderer spins at high CPU while codex MCP servers are idle
- Codex Desktop on macOS becomes progressively slow and causes system-wide lag during long MCP-heavy sessions #12709 Codex Desktop on macOS becomes progressively slow and causes system-wide lag during long MCP-heavy sessions
- Codex desktop app generates excessive file I/O (~5,500-12,000 openat/sec), causing severe CPU load on systems with endpoint security software #12644 Codex desktop app generates excessive file I/O, causing severe CPU load
- Codex App becomes choppy when session history contains a large user message #22283 Codex App becomes choppy when session history contains a large user message
- Thread navigation/loading slows from unbounded metadata and eager large-history hydration #21211 Thread navigation/loading slows from unbounded metadata and eager large-history hydration
- app-server loads ALL session files on every thread/list call — causes high CPU, slow startup, and background token waste #22411 app-server loads ALL session files on every thread/list call
- Desktop performance collapses in profiles with a few very large local conversation histories (typing, scrolling, thread list, random exits) #18693 Desktop performance collapses in profiles with a few very large local conversation histories
- Codex Desktop 26.506.31421 main Electron process pegs ~280% CPU on macOS while app-server is idle #22053 Codex Desktop main Electron process pegs CPU while app-server is idle
- Codex Desktop on macOS loses message hover after long session; main/renderer/GPU stay hot until app restart #23849 Codex Desktop on macOS loses message hover after long session; main/renderer/GPU stay hot
- Desktop: thread.archived silently reset to 0 on next app cycle for any thread whose sessions/<rollout>.jsonl exists #23851 Desktop:
thread.archivedcan be reset by reconciliation after direct DB archive
This report may be the same family as #21211 / #22411, but the local evidence
here points specifically at active state_5.sqlite metadata and query/index
shape.
Workaround / Mitigation Script
I also prepared a local mitigation script that may help other affected users
diagnose and temporarily reduce the active-list load.
It does not patch Codex itself. It is a user-side workaround.
Script gist:
https://gist.github.com/kangminlee-maker/f933aade364a02b176d899e5c8a2486d
The script:
- reports active thread metadata size from
~/.codex/state_5.sqlite - detects repeated high-volume active thread groups dynamically
- keeps one real active representative thread per group
- archives older sibling threads
- writes
archive_groups.jsonlso archived siblings can be searched/restored - can create optional helper SQLite indexes for the observed thread-list query
- can copy/move old archived rollout JSONL files to cold storage
- defaults to dry-run and backs up
state_5.sqlitebefore applying changes
Safety notes:
- Do not use this for SQLite corruption or migration checksum mismatch issues.
- Run the report/dry-run first.
- Close Codex before applying changes.
- Keep the generated backup.
- Direct SQLite edits are unsupported and may interact with Codex reconciliation.
Example dry-run:
python3 scripts/codex_state_batch.py batch --policy config/sample-policy.jsonExample apply after closing Codex:
python3 scripts/codex_state_batch.py batch --policy config/sample-policy.json --applyDry-run result on my affected profile:
candidate archive threads: 1,000
candidate metadata payload chars: 140,384,001
dynamic groups detected: 10
representative manifest entries planned: 5
cold archive candidates: 2,447 rollout JSONL files, 813,736,111 bytes
The mitigation is attached as a small script package. It is meant as diagnostic
evidence and a temporary local workaround, not as an official fix.
Expected Behavior
Codex Desktop should keep thread-list/sidebar/reconnect paths bounded:
- list views should read small metadata projections only
- very large
title/preview/first_user_messagefields should be capped
or summarized - thread history should be hydrated lazily
- active sidebar queries should use indexes that satisfy both filtering and
ordering - archive state should remain stable across reconciliation
- heavy local profiles should not keep app-server / renderer / GPU hot while
idle
Actual Behavior
Large active local metadata causes expensive repeated local processing. Depending
on the profile and app state, the visible bottleneck can appear in SQLite,
app-server CPU, Electron main/IPC, renderer, or GPU, but the common source is the
unbounded local-history/list path.
Activity
github-actions commentedon May 26, 2026
Potential duplicates detected. Please review them and close your issue if it is a duplicate.
Powered by Codex Action
kangminlee-maker commentedon May 26, 2026
Thanks for the duplicate suggestions. I reviewed #24427, #23919, and #23072.
I agree they are related to the same broader Desktop performance family, especially around many local sessions, long histories, and high CPU. I am going to leave this issue open for now because the distinguishing evidence here is the local state/list path:
state_5.sqliteactive metadata size measurementslogs_2.sqliteTRACE/log growth evidencethread_goal_updatedeventsIf maintainers prefer one canonical tracking issue, I am happy for this to be closed as a duplicate, but I wanted to preserve the state DB/query-plan evidence because it may help isolate the product-side fix.
jshaofa-ui commentedon May 26, 2026
kangminlee-maker commentedon May 26, 2026
Thanks for the summary and proposed fixes. I agree with the general direction: bounded list metadata, lazy loading, and limiting stored preview/title size would likely help.
One small clarification so the evidence stays precise:
state_5.sqlitequery shape, its query plan, and the large active metadata payload that the list path has to scan/sort.title,preview, andfirst_user_message; I did not see athread_namecolumn in this localthreadstable.So I would phrase the product fix as: keep the thread list bounded by using small indexed projections, cap or summarize large metadata fields, lazy-load full history/large metadata only when opening a specific thread, and make any cleanup/archival behavior explicit and reversible.
35ujq435jq45 commentedon May 26, 2026
Only by setting additional indices, I got fewer freezes:
Make a backup
Add indices
35ujq435jq45 commentedon May 26, 2026
Thanks for digging into this. I think this issue is very relevant to the slow
thread/listbehavior I am seeing in a related Codex Desktop freeze report.I checked the affected remote Codex profile, and it lines up closely with the query-shape concern described here:
~/.codex/state_5.sqlite: ~26 MB plus WAL2,4362,4288title,preview, andfirst_user_message:19,859,906characters~/.codex/sessions: ~1.5 GB~/.codex/logs_2.sqlite: ~605 MBThe remote
thread/listquery plan also matches the inefficient shape called out in this issue:That makes me think this issue likely explains a substantial share of the 10-25 second
thread/listcalls captured in the freeze investigation.There may still be a distinct Desktop/remote-SSH failure mode layered on top of it, though. In the related captures, the user-visible freeze tends to appear around remote SSH app-server reconnect/recovery. The logs show
app_server_connection.closed code=1006, app-server proxy termination, reconnect/recovery, and then clustered longthread/list/thread/resumecalls across multiple large remote conversations.So my current read is: this issue may be the underlying scalability problem, while the remote SSH reconnect path can amplify it into a visible Codex Desktop freeze.
kangminlee-maker commentedon May 27, 2026
Update after the latest Codex Desktop update:
I updated/restarted Codex Desktop and rechecked this issue on:
26.519.81530/ build3178rust-v0.134.0My read is that this release contains adjacent changes, but not a direct fix for the bottleneck described here. The changelog includes work around local conversation history search, thread content search, tracing/analytics, websocket/session behavior, and compaction. I did not find an explicit change for the active thread-list /
state_5.sqlitemetadata scan/sort path.The issue still appears reproducible in my local profile after restart:
state_5.sqlitecurrently has8,657active threadstitle+preview+first_user_messagepayload is still about202,756,007characters4,291threads and about468,871,981charactersupdated_atactive-list query path still uses a temp B-tree sort in my DB:One caveat: my local DB still has helper indexes from the temporary mitigation, so any improvement on the
created_atpath should not be treated as evidence that the product fix landed. Theupdated_atpath above is why I think this issue remains valid after the update.kangminlee-maker commentedon May 27, 2026
A note on the additional-index workaround from @35ujq435jq45: I think this is a useful tactical mitigation and it matches what I saw locally.
Composite indexes that include
archivedplus the list sort key can reduce or avoid the expensive active-list scan/sort path, especially when the query can satisfyORDER BY ... DESC, id DESCfrom the index. In my local mitigation, helper indexes around the active preview/list path made thecreated_atquery much faster.I would still treat this as a workaround rather than the full product fix:
model_provider,preview <> '', workspace/cwd filters, and whether the list is ordered bycreated_at_msorupdated_at_msUSE TEMP B-TREE FOR ORDER BYtitle,preview, andfirst_user_messagefields can still move a lot of dataSo I think the index workaround is valuable evidence and a good short-term mitigation for affected users, but the underlying scalability issue should still be fixed in the app/server query and metadata model.
w2jmoe commentedon May 27, 2026
kangminlee-maker commentedon May 27, 2026
One design thought after the discussion above:
Keep the full history, but do not make the session list carry its weight. Add a buffer between history and UI, and split "active" into more than one state.
The current behavior seems to make
archived=0do too much work. "Not archived" can mean:Those are very different states, but they all appear to end up in the same active list surface.
I think a durable fix would be to keep the current local history/resume behavior, but put a small bounded list layer in front of it:
Indexes are a useful workaround for the current query shape, but they do not fully solve the design issue. They make the heavy path faster; they do not make the path lighter.
The main point is: the sidebar should show a bounded view of the local history, not become the place where the growing local history is repeatedly paid for.
5 remaining items
Nicolas0315 commentedon Jun 28, 2026
Follow-up correction on the user-side mitigation above:
After moving the 8 large JSONL files out of active
~/.codex/sessions, the user observed that even recent sessions were no longer visible in the Codex UI. I restored all 8 files back to their original active session paths and SHA256-verified the restored files.Post-restore checks:
~/.codex/sessions: back to 696 JSONL files / about 1262 MiBstate_5.sqlitethread rows all hadrollout_pathfiles present after restoreSo the quarantine experiment is useful as evidence that active JSONL scan-set size affects load, but it is not a safe user-side workaround for normal use because Codex Desktop appears to depend on those files being present for session visibility/navigation. The product-side fix still needs to be lazy loading / indexing / virtualization rather than asking users to move session files.
Nicolas0315 commentedon Jun 28, 2026
Follow-up after testing a safer user-side mitigation:
Manual filesystem quarantine is not a safe default workaround. I moved 8 old large JSONL files out of active
~/.codex/sessionsonly after copying and SHA256-verifying them, and it reduced the benchmark cost, but the user observed that even recent sessions disappeared from the Desktop UI. I restored all 8 files to their original paths and verified SHA256 matches. So removing/moving active JSONL files can preserve bytes but still break usability.Safer local mitigation used afterward:
codex archive <thread-id>for local threads older than 7 days.state_5.sqlitebefore the batch and wrote a restore manifest/runbook.codex_app.list_threads(limit=50)returned the recent local 7-day window again.Conclusion: official archiving is a much better user-side mitigation than moving JSONL files, but the product-side fix is still needed: thread list/startup/resume should not require parsing large active JSONL files, and transcript hydration should be bounded/lazy.
Nicolas0315 commentedon Jun 28, 2026
Correction / additional finding:
After running
codex doctor --json, I found that the earlier direct SQLite archive experiment left a parity warning:rollout_pathunder active~/.codex/sessions/...So direct DB updates are not a safe mitigation, even if recent threads appear again and data bytes are preserved. They can leave archived DB rows whose rollout files are still in the active sessions tree.
Current safer plan:
codex unarchive <id>thencodex archive <id>so Codex itself normalizes state and file placement.codex doctor --json, active thread list, and the benchmark before any all-thread repair.I also updated the metadata-only benchmark script to include
logs_2.sqlitewrite-rate sampling. Current latest sample still shows high-frequency log persistence:logs_write_sample: 10s, 1,969 rows, 196.86 rows/sTRACE logcodex.exe app-serverpeaked at about 130% of one coreConclusion: session JSONL archive reduces the largest parse bottleneck, but there are two remaining product-side issues: archive/file parity robustness and high-rate TRACE/DEBUG log persistence.
Nicolas0315 commentedon Jun 28, 2026
Follow-up on the local visibility regression after history-load mitigation:
state_5.sqlitehas 42 non-archived rows with existing files under~/.codex/sessions; 41 files have mtimes within the last 7 days, totaling about 106 MiB.codex doctor --jsonstill reports 499 archived DB rows whose rollout files remain under activesessionsrather thanarchived_sessions.threadshashas_user_event=0, including all non-archived recent rows, whilefirst_user_message,preview, andtitleare populated for all 42 active rows.has_user_eventor cached interactive-thread state, the UI can show no recent sessions even though the active rollout files are present.state_5.sqlitewhile Desktop/app-server are running. Any local repair is gated on closing Codex, copyingstate_5.sqlite,state_5.sqlite-wal, andstate_5.sqlite-shm, then applying a small pilot and re-runningcodex doctor.This is adjacent to the original load issue: user-side archiving can reduce JSONL parse cost, but the product needs a reliable metadata/index repair path so the list does not regress into an empty or stale state.
Nicolas0315 commentedon Jun 28, 2026
Additional local mitigation details after the visibility regression:
threads.has_user_eventflag is0for every row. A metadata-only scan of recent active JSONL files found user-like event types/roles in all recent active files checked; no message bodies or prompt text were exported.has_user_event=1only for recent non-archived rows whose JSONL structure contains user-like event types/roles;codex unarchive/codex archivecommands for rows whose DB archived flag and rollout file location disagree.codex doctor, restart Desktop, confirm recent sessions are visible, then proceed further only if the pilot is healthy.This reinforces the product-side need for a safe, built-in state/index repair path in addition to lazy/bounded history loading. User-side archiving can reduce parse load, but stale metadata flags can make the Desktop list look empty even when rollout files are present.
Nicolas0315 commentedon Jun 28, 2026
Follow-up from the same Windows Codex Desktop profile (2026-06-29 03:38 JST): recent sessions now appear to be hidden by thread metadata, not missing files.
Read-only health check:
threads: total 702, active 42, archived 660recent_active_has_user_event: 0 / 39recent_active_metadata_nonempty: 39 / 39 (first_user_message/preview/titlepopulated)has_user_event=0~/.codex/sessionsInterpretation: this looks like a state/index metadata regression. The recent session JSONL files still exist, but if Desktop filters/list-builds by
has_user_event, the UI can look empty even for sessions newer than 7 days.Local mitigation prepared but not applied live: a stopped-Codex repair pilot that backs up
state_5.sqlite+ WAL/SHM, verifies SQLite integrity, then updates onlythreads.has_user_event=1for recent active rows whose JSONL structural scan confirms user events. I am not mutating the DB while Codex Desktop/app-server is running.Nicolas0315 commentedon Jun 28, 2026
Follow-up local mitigation from the same Windows Codex Desktop profile (2026-06-29 03:53 JST): disabled MCP config did not stop already-running helper processes until I explicitly cleaned up the old app-server children.
Setup:
codex mcp list: chrome-devtools, playwright, filesystem, codebase-memory-mcp disabled in configapp-serverprocess tree and only matching disabled MCP server command patternsApply result:
Benchmark comparison, before -> after cleanup:
logs_2.sqlitewrite sample: 6.38 rows/s -> 0 rows/sThis suggests two related product improvements:
Nicolas0315 commentedon Jun 28, 2026
Follow-up after the disabled MCP helper cleanup: I also measured the remaining plugin helper footprint in the same Windows Desktop session.
After disabled MCP helpers were removed, Codex app-server still had 21
plugin-server-*child processes. Reading only process metadata/current directories showed:data-analytics: 7 processescreative-production: 7 processescodex-security: 7 processesThese were enabled plugin helpers and not needed for the current performance investigation, so after a read-only second review and an explicit dry-run selecting exactly 21 processes, I stopped only those three plugin groups. Rollback is restarting Codex Desktop.
Benchmark comparison before any helper cleanup -> after disabled MCP + optional plugin helper cleanup:
logs_2.sqlitewrite sample: 6.38 rows/s -> 0 rows/sFeature tradeoff: stopping enabled plugin helpers may make those plugin-backed tools unavailable until respawn/restart. Product-side improvement would be to lazy-start plugin helpers on first actual use, and terminate/reclaim idle helpers after a timeout, instead of keeping one process per server helper alive in the default Desktop session.
Nicolas0315 commentedon Jun 28, 2026
Additional local cleanup from the same Windows Desktop session: after disabled MCP and optional plugin helpers were stopped, six
node_repl.exeprocesses remained directly under the Codex app-server. They were not needed for this performance investigation, so after read-only review and confirming no in-flight MCP tool call, I stopped all six together.Result:
node_repl: 7 -> 0 in the benchmarked process kind countsTradeoff: browser/chrome/node-repl-backed tools may be unavailable until respawn/restart. Product-side improvement: avoid keeping an idle node_repl pool alive across sessions, or reclaim idle node_repl workers after a timeout.
Latand commentedon Jun 30, 2026
Adding a Linux Desktop data point from 2026-06-30. I am running the official Codex Desktop bundle through the community Linux wrapper, so treat the platform layer with that caveat. The failure shape looks very close to the local-history / thread-list / hydration path described here.
Environment:
User-visible symptom:
Trigger pattern:
prod deploy.Representative process snapshot shortly after relaunch, while the app was usable again but still hot:
Local profile sizes from the same machine:
Log signals observed locally:
My read is that search/open of old local threads can push an oversized serialized thread payload through the app-server -> Electron renderer path. In this profile it manifested as a renderer/main/GPU hot loop and an unclickable Desktop window. This is adjacent to the unbounded local-history/list processing described in this issue, with thread search as the user-level trigger.
Expected behavior:
loraldx commentedon Jul 13, 2026
Adding a sanitized Windows data point at a smaller scale than the original report.
Environment:
Local state summary:
The renderer and app UI remained measurably active while idle. Because the currently opened thread was modest in size, the slowdown does not appear attributable only to rendering one exceptionally large transcript. The aggregate active-thread index, reconciliation work, spawn-edge state, or eager local-history hydration may also contribute.
This suggests the scalability threshold can become visible at hundreds of active threads combined with subagent relationships, not only at several thousand threads or one unusually large conversation.
Useful mitigations at the product level could include bounded sidebar projections, lazy hydration of inactive thread metadata, incremental reconciliation, and first-class bulk archive/cleanup controls.
Thread titles, identifiers, prompts, local paths, account information, and raw session files have been omitted.
Appiah1907 commentedon Aug 2, 2026
Windows Codex Desktop corroboration (sanitized)
Environment: Windows desktop app package 26.727.6591.0; Ultra; multi-agent enabled with a cap of 8.
Observed on an affected profile:
open.This reproduces the active-thread/local-history scalability symptoms in this issue and adds a lifecycle angle: internal helper/approval/harness flows appear able to create retained child-thread and helper-process state too. Please ensure terminal reconciliation covers ordinary subagents and internal helper flows, and that active-list/sidebar work excludes completed children promptly.
No local paths, transcripts, thread IDs, or configuration contents are included here. Redacted diagnostics are available if maintainers request them.
Appiah1907 commentedon Aug 2, 2026
Additional Windows Desktop finding (sanitized)
The current behavior makes sustained professional development work nearly impossible: the machine becomes visibly choppy while Codex has no intended code task running, and the user cannot reliably continue or reopen work threads.
New observations after controlled cleanup/restart:
openeven after their child threads were archived. The active orchestration index is therefore not reconciling terminal child state.notifyhook repeatedly reappears in the local configuration after app restarts despite prior removal. The observed hook had previously failed with Windows error 206 (overlong command line/file name). This may indicate configuration reconciliation or another writer restoring stale state.This is not a request to disable multi-agent workflows. The expected professional behavior is: terminal children and spawn edges are reconciled automatically; internal helper/review flows are not retained as active work; hook configuration remains stable; and the desktop renderer remains quiet when no task is running.
Please treat this as a reliability/productivity issue for the Windows desktop client, not only a performance optimization.
Dylan997S commentedon Aug 17, 2026
macOS SSH data point: opening one large remote transcript can pin the renderer at 89% CPU
This is a reproducible remote-thread variant of the unbounded history/hydration problem.
Environment
26.810.52044(build6662)0.148.0-alpha.926.5(25F71), Apple Silicon0.147.0Reproduction and measurements
The SSH connection and remote app-server were healthy (
connected), and direct SSH pluscodex --versioncompleted normally. Clicking an existing remote thread caused the app to remain on the loading view indefinitely.At the time of the freeze:
88.7%CPU continuously;0%;46-48%for several minutes;The affected active remote rollouts were:
tokens_usedThis was not an SSH latency failure. The renderer loop started specifically when opening the large transcript. Archiving those three threads in the remote
state_5.sqliteand moving the rollouts toarchived_sessionsmade the project usable again; smaller active threads remained available.There was also repeated
git hash-object --stdin-pathswait_failedactivity while the affected project was selected, but repairing the repository refs alone did not prevent the freeze. The transcript size/hydration path remained a separate trigger.Expected behavior
Related: #35096 (renderer freezes while restoring SSH remote threads). This data point adds macOS, direct per-thread size measurements, and a successful archive-based isolation test.
Disclosure: this report was drafted with Codex assistance from sanitized process, SQLite metadata, and file-size measurements. No transcript content or private paths are included.
idle-paladin commentedon Sep 3, 2026
Summary
I investigated a reproducible Codex Desktop problem where the Windows UI became
unresponsive or nearly unresponsive around prompt submission, turn completion,
thread updates, and subsequent window interaction.
This was reproduced on Windows. The Markdown-related finding below is
independently supported by the trace-confirmed macOS investigation in #35604,
while the user-visible freeze pattern is also similar to #33524.
In my tested environment, the freezes were eliminated after applying three
targeted changes to a copy of Codex Desktop. The immediate improvement was most
obvious after removing full Markdown AST parsing from display-only title and
preview processing. Two additional changes removed major amplification paths:
re-reading append-only rollout JSONL from byte zero and reprocessing every
unchanged thread on each observation update.
This is a report of a locally verified result, not a claim that every reported
Windows freeze has the same cause. The exact code structure must be confirmed
again in current releases.
Tested environment
Observed symptoms before the changes
Not Respondingstate.a turn.
Finding 1: full Markdown AST parsing in a display-only plain-text path
A main-process path used for thread names, preview-derived display titles, and
desktop notification text converted the complete input to a Markdown AST merely
to produce short display text. Large previews therefore entered a relatively
expensive parser even though full Markdown rendering was unnecessary.
The message-body renderer was a separate path and was not changed.
Local workaround
For these display-only strings, I replaced full Markdown AST construction with
lightweight whitespace normalization.
An upstream implementation could alternatively use a bounded lightweight
plain-text extractor. The important properties are:
short title or notification.
Result
This change alone produced an immediate and dramatic responsiveness improvement
in the tested environment.
Finding 2: append-only rollout JSONL was repeatedly scanned from byte zero
The rollout aggregation path could reopen local rollout JSONL files and parse
their complete historical contents again. The files observed during the test
were append-only: new events, including compaction-related events, were appended
as new lines rather than rewriting previous lines.
One measured cold scan covered 69 files totaling approximately 1.133 GiB and
took approximately 92.5 seconds in the tested environment. This path may not be
the direct cause of every prompt-submission stall, but it is an independent and
very large amplification path during history aggregation or resume work.
Local workaround
I retained per-file aggregation state and resumed parsing at the first unread
byte.
Additional safeguards used by the local workaround:
established safely.
A production implementation should use whatever stable file-generation or
append-only invariant the app-server owns instead of depending on version-
specific minified code.
Finding 3: unchanged thread observations were fully converted and emitted
The thread observation/catalog path received a page of threads and repeatedly
converted all entries, regenerated display titles, and notified listeners even
when only zero or one thread had changed.
In one measurement:
This multiplied the cost in Finding 1.
Local workaround
I cached the version of each observed thread and emitted only changed catalog
entries.
The local workaround bounded these caches to 512 entries and cleared them on
dispose.
Result
For the measured case, downstream work fell from 100 threads and approximately
7.37 million preview characters to either no notification or one changed entry
containing 41 characters. Calls to the expensive display-text path fell from
approximately 200 to approximately 2.
Combined result
With all three changes applied:
reproducible UI freezes in this environment.
During validation, a separate unmodified official instance was once left
running without a visible window. It continued processing the same local task
history and made the machine appear to freeze even though the modified instance
was idle. After all unmodified and older test instances were closed and only the
final modified copy was running, the residual stalls disappeared. Anyone
testing this should verify that only one top-level Codex Desktop instance is
active.
Agent-readable investigation and workaround guidance
The following section is intended to let another affected user give this report
to a local coding agent. It deliberately describes semantic targets rather than
release-specific minified identifiers.
Safety requirement
app.asarand its correspondingapp.asar.unpackeddirectory.app.asarover a newer release.back by simply closing and deleting the test copy.
Semantic targets
Validation checklist
search, and notifications.
work and repeated rollout aggregation reads only newly appended bytes.
aggregation becomes inconsistent.
Notes for maintainers
The release-specific local patch was made against minified packaged JavaScript,
so names observed in that build are intentionally omitted here. The semantic
boundaries and measured amplification are more useful for locating the original
source: