Reappearing crash loop - Codex UI became completely unusable
What version of the Codex App are you using (From “About Codex” dialog)?
I can't even open this due to this bug, but it was latest version as of May 25th, 2026
What subscription do you have?
Pro
What platform is your computer?
Darwin 25.4.0 arm64 arm
What issue are you seeing?
Crash loop showed this:
Uncaught Exception:
RangeError: Invalid string length
at Ed.write (/Applications/Codex.app/Contents/Resources/app.asar/.vite/build/workspace-root-drop-handler-Bom6Z7sW.js:292:21)
at Socket.onStdoutData (/Applications/Codex.app/Contents/Resources/app.asar/.vite/build/workspace-root-drop-handler-Bom6Z7sW.js:292:4146)
at Socket.emit (node:events:509:28)
at addChunk (node:internal/streams/readable:563:12)
at readableAddChunkPushByteMode (node:internal/streams/readable:514:3)
at Readable.push (node:internal/streams/readable:394:5)
at Pipe.onStreamRead (node:internal/stream_base_commons:189:23)
It appeared again upon startup.
Immediately after clicking OK, it appears again, and the app has become unusable.
Restarting the app causes it to appear again. There is no obvious way to get it to disappear.
I can't click anything in the Codex UI other than OK, but it reappears immediately.
<img width="1046" height="883" alt="Image" src="https://github.com/user-attachments/assets/f0386f30-4978-4dc8-bc7c-e61664b568c5" />
Codex will also only shut down via Force Quit now.
I also tried to reset session state via the following, but it didn't solve the problem:
osascript -e 'quit app "Codex"' 2>/dev/null || true
pkill -f '/Applications/Codex.app' 2>/dev/null || true
REC="$HOME/.codex/recovery-$(date +%Y%m%d-%H%M%S)"
mkdir -p "$REC"
mv "$HOME/Library/Application Support/Codex/Session Storage" "$REC/" 2>/dev/null || true
mv "$HOME/Library/Application Support/Codex/Local Storage" "$REC/" 2>/dev/null || true
mv "$HOME/.codex/.codex-global-state.json" "$REC/" 2>/dev/null || true
open -a Codex
I was able to clear it by running this script:
osascript -e 'quit app "Codex"' 2>/dev/null || true
pkill -f '/Applications/Codex.app' 2>/dev/null || true
REC="$HOME/.codex/recovery-$(date +%Y%m%d-%H%M%S)"
mkdir -p "$REC/bad-sessions"
find "$HOME/.codex/sessions" -type f -name '*.jsonl' -mtime -7 -size +25M -exec ls -lh {} +
BAD="$(find "$HOME/.codex/sessions" -type f -name '*.jsonl' -mtime -7 -size +25M -print | xargs ls -t 2>/dev/null | head -1)"
echo "Quarantining: $BAD"
if [ -n "$BAD" ]; then
base="$(basename "$BAD" .jsonl)"
THREAD_ID="$(printf '%s\n' "$base" | awk -F- '{print $(NF-4)"-"$(NF-3)"-"$(NF-2)"-"$(NF-1)"-"$NF}')"
mv "$BAD" "$REC/bad-sessions/"
sqlite3 "$HOME/.codex/state_5.sqlite" "update threads set archived=1, archived_at=strftime('%s','now') where id='$THREAD_ID';"
fi
open -a Codex
What steps can reproduce the bug?
I stopped a thread. It's otherwise not known what caused this.
What is the expected behavior?
Codex should be usable ... or, it should at least be possible to dismiss the error.
Additional information
_No response_
9 Comments
Potential duplicates detected. Please review them and close your issue if it is a duplicate.
Powered by Codex Action
Same issue
Uncaught Exception:
RangeError: Invalid string length
at Ed.write (/Applications/Codex.app/Contents/Resources/app.asar/.vite/build/workspace-root-drop-handler-DJwLZgXt.js:292:21)
at Socket.onStdoutData (/Applications/Codex.app/Contents/Resources/app.asar/.vite/build/workspace-root-drop-handler-DJwLZgXt.js:292:4146)
at Socket.emit (node:events:509:28)
at addChunk (node:internal/streams/readable:563:12)
at readableAddChunkPushByteMode (node:internal/streams/readable:514:3)
at Readable.push (node:internal/streams/readable:394:5)
at Pipe.onStreamRead (node:internal/stream_base_commons:189:23)
I just got this right after refreshing an installed plugin cache. Not sure if it's related yet. What was different though is the dialog box didn't reappear after I clicked OK this time.
It happened again where I couldn't make it go away....
Error message this time:
Notable findings:
I think the ideal fix is to make session history bounded, lazy, and attachment-aware so a single poisoned/huge thread can never crash the whole app.
The concrete root problem appears to be a combination of oversized local rollout JSONL files, inline base64 image/tool payloads, and large full-state hydration over Electron/IPC. The linked crash shows RangeError: Invalid string length in the main process while handling stdout, and a related report attributes that to accumulating a rollout load into one JavaScript string near V8’s single-string limit.  Other reports show the same design issue below the hard crash threshold: image-heavy rollouts around 183 MB or 747 MB can hang or slow the app because large data:image/...;base64 payloads are stored inline in session history. 
I’d structure the upstream fix like this:
Codex should externalize images, screenshots, generated images, and large tool outputs into blob/file storage, then put only lightweight references in the JSONL/session record.
Instead of records containing this kind of shape:
they should contain something closer to:
That is the biggest architectural win. As per ChatGPT, one macOS report says removing only embedded image payloads shrank a rollout from 747 MB to 2.3 MB while preserving the JSONL records and metadata.  Another report found inline browser/computer-use screenshots being persisted in function_call_output, causing huge session state and later resume failures; its suggested fix is also to store blobs out-of-line and persist only references. 
Startup should only read a small index: thread id, title, last modified time, last message preview, status, and maybe a compact summary. It should not fully parse every rollout in ~/.codex/sessions.
When the user opens a thread, Codex should stream-read JSONL line by line, hydrate only the visible window plus recent context, and use virtualization/pagination for the rest. JSONL is line-delimited, so it is naturally compatible with incremental parsing; the issue report explicitly suggests stream-parsing rollouts rather than buffering the whole file into one string. 
A good invariant would be:
The broken thread can fail to load, but it should not take down the app.
The crash path appears to involve the app-server/runtime streaming data through stdout and the Electron main process aggregating it into one JavaScript string. The related issue says this hits V8’s max single-string length around 512 MB.  That transport needs hard boundaries.
The ideal protocol would use one of:
and it should enforce maximums, for example:
There is a related VS Code extension report where a completed long thread repeatedly broadcast full conversationState snapshots of about 116 MB, causing high CPU; the suggested upstream fix there is to avoid full snapshots for completed/long threads and use bounded, paginated, patch-only sync instead.  Same principle applies to Desktop.
For existing users, Codex needs a safe migration path, not just a forward fix. On startup or before opening a session, it should scan metadata and detect risky records without fully loading them.
A repair pass could:
The UI should expose this as something like:
That would turn today’s manual shell surgery into a first-class recovery workflow.
Even with all of the above, Codex should assume local state can be corrupt or adversarially large.
The app should catch RangeError, JSON parse failures, oversized-line errors, missing blobs, and malformed session index entries at the session boundary. The user experience should be:
not:
The linked issue’s expected behavior says the app should either load arbitrary-size sessions or detect the oversized rollout, refuse to load that one session, and continue starting normally so other threads remain accessible. 
Codex should also prevent new poisoned sessions from being written. That means caps before persistence, before replay, and before rendering.
For example:
The meta-bug report groups these as a broader “unbounded session/turn state” design problem affecting freezes, context bloat, and lost active-turn control, not just one crash. 
The upstream fix should include tests that simulate exactly the failure modes users are reporting:
Success criteria should be:
So, in one sentence: the ideal fix is to externalize large blobs, stream and paginate session state, bound all IPC/stdout messages, and isolate/repair bad sessions instead of letting one oversized rollout crash the main process.
This script can be pasted into a Mac terminal to identify large files before a crash:
I ran deeper profiling and may have pinned down the real problem tightly enough to file a precise Codex bug:
SkyComputerUseService, the Codex Computer Use helper, is retaining hugeNSData/NSConcreteDatabyte buffers from Codex app-server thread-event IPC.Key evidence:
Codexparent process was only about253 MB;SkyComputerUseServicewas27 GB.vmmapshowed36.8 GBallocated in the default malloc zone, not mapped files.heapshowed36.7 GBasNSConcreteData (Bytes Storage).231allocations of128 MB=28.9 GB61allocations of64 MB=3.8 GB2allocations of256 MB=512 MBsampleshowed the active queue asCodexAppServerThreadEventObserver.connection, reading viaNSFileHandle.read(upToCount:), allocatingNSData, then decoding withJSONDecoder.lsofshowed the helper connected to the main Codex process through/var/.../T/codex-ipc/ipc-501.sock.CodexAppServerThreadEventObserver.swift,thread-stream-state-changed,thread-follower-start-turn, and Skysight/event-stream components.So the refined diagnosis is: My use case is generating enough long-running, high-volume Codex thread activity that Codex Computer Use’s thread-event observer receives large IPC JSON frames and retains their
Databacking stores. The memory also stayed retained after the worker exited, so this is not just temporary buffering.The one caveat:
malloc_historycould not recover allocation stacks because the helper was not launched withMallocStackLogging=1. So I do not have the exact allocation source line, but the process, heap type, queue, IPC path, and decode stack are all aligned.The root fix belongs in Codex Computer Use: release/stream/drop old thread-event frame
Data, avoid retaining decoded frame buffers, or bound the event observer’s history.The app needs a bounded session-history ingest path, not full-state hydration.
Useful guardrail: index each rollout by session id, file path, byte offsets, total bytes, largest event bytes, binary/base64 artifact refs, and parser status. Load metadata first, page event bodies lazily, and quarantine a single oversized/poisoned rollout with a recovery receipt instead of letting it crash the whole app on startup.
Generated with ax - https://github.com/Necmttn/ax