Reappearing crash loop - Codex UI became completely unusable

Open 💬 9 comments Opened May 26, 2026 by devinbost
💡 Likely answer: A maintainer (github-actions[bot], contributor) responded on this thread — see the highlighted reply below.

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_

View original on GitHub ↗

9 Comments

github-actions[bot] contributor · 1 month ago

Potential duplicates detected. Please review them and close your issue if it is a duplicate.

  • #23673
  • #23174

Powered by Codex Action

markitics · 1 month ago

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)

devinbost · 1 month ago

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.

devinbost · 1 month ago

It happened again where I couldn't make it go away....
Error message this time:

Uncaught Exception:
RangeError: Invalid string length
at Ad.write (/Applications/Codex.app/Contents/Resources/app.asar/.vite/build/src-BRBmN298.js:285:21)
at Socket.onStdoutData (/Applications/Codex.app/Contents/Resources/app.asar/.vite/build/src-BRBmN298.js:285:4137)
at Socket.emit (node:events:508: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)
devinbost · 1 month ago

Notable findings:

Large Codex session files:
-rw-r--r--@ 1 devin.bost  staff   909M Jun  4 11:24 /Users/devin.bost/.codex/sessions/2026/05/31/rollout-2026-05-31T16-39-07-019e7ff9-f0b3-7fe3-beac-e3382df879b6.jsonl
-rw-r--r--@ 1 devin.bost  staff   135M Jun  4 11:12 /Users/devin.bost/.codex/sessions/2026/06/02/rollout-2026-06-02T11-52-01-019e893f-d0a2-7db0-ac03-d41a0e5df5ab.jsonl
-rw-r--r--@ 1 devin.bost  staff   127M Jun  4 11:24 /Users/devin.bost/.codex/sessions/2026/06/01/rollout-2026-06-01T13-04-20-019e845b-a9c9-7f83-a198-759832bf1ceb.jsonl
-rw-r--r--@ 1 devin.bost  staff   125M Jun  1 13:53 /Users/devin.bost/.codex/sessions/2026/05/31/rollout-2026-05-31T16-38-32-019e7ff9-6b26-7140-8944-248017539bd5.jsonl
-rw-r--r--@ 1 devin.bost  staff    61M Jun  3 13:22 /Users/devin.bost/.codex/sessions/2026/06/02/rollout-2026-06-02T12-26-32-019e895f-6b98-76c1-950d-0bfe9493587e.jsonl
-rw-r--r--@ 1 devin.bost  staff    48M Jun  3 09:59 /Users/devin.bost/.codex/sessions/2026/06/02/rollout-2026-06-02T23-27-49-019e8bbc-d68b-7e90-8f67-490c90d9363f.jsonl
-rw-r--r--@ 1 devin.bost  staff    47M Jun  3 09:57 /Users/devin.bost/.codex/sessions/2026/06/02/rollout-2026-06-02T23-27-46-019e8bbc-cd78-7441-b997-eb66e6327b3d.jsonl
-rw-r--r--@ 1 devin.bost  staff    42M Jun  3 09:56 /Users/devin.bost/.codex/sessions/2026/06/02/rollout-2026-06-02T23-27-45-019e8bbc-c927-7fd1-b2ec-5ece107b49e6.jsonl
-rw-r--r--@ 1 devin.bost  staff    26M Jun  4 07:27 /Users/devin.bost/.codex/sessions/2026/06/03/rollout-2026-06-03T09-57-49-019e8dfd-9fbc-7b53-90eb-1b6cfff8cdec.jsonl
devinbost · 1 month ago

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:

  1. Stop storing binary payloads inline in replayable session history

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:

{
  "type": "response_item",
  "payload": {
    "content": [
      {
        "type": "input_image",
        "image_url": "data:image/png;base64,....huge...."
      }
    ]
  }
}

they should contain something closer to:

{
  "type": "response_item",
  "payload": {
    "content": [
      {
        "type": "image_ref",
        "blob_id": "sha256:...",
        "mime": "image/png",
        "bytes": 2481231,
        "width": 1024,
        "height": 1024,
        "thumbnail_blob_id": "sha256:..."
      }
    ]
  }
}

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. 

  1. Stream and page session loading instead of hydrating the whole thread

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:

Codex Desktop can launch and show the sidebar even if one session file is 2 GB, malformed, or full of giant image records.

The broken thread can fail to load, but it should not take down the app.

  1. Replace giant stdout / IPC snapshots with framed, bounded messages

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:

  • length-prefixed binary/JSON frames
  • NDJSON records with backpressure
  • chunked payload frames with explicit max frame size

and it should enforce maximums, for example:

  • max stdout frame: 8–32 MB
  • max IPC message: 5–16 MB
  • max JSONL line accepted without externalization: maybe 1 MB
  • max replayed tool-output text: capped/summarized

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.

  1. Add a built-in repair/compaction migration

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:

  1. Read each JSONL record line by line.
  2. Detect data:image/*;base64 fields and oversized payload.result fields.
  3. Decode/store those blobs under ~/.codex/blobs/ or ~/.codex/generated_images/.
  4. Replace inline bytes with image_ref/blob_ref records.
  5. Preserve prompts, call ids, saved_path, timestamps, turn ids, and summaries.
  6. Write a compacted rollout atomically.
  7. Keep the original in a recovery/quarantine folder.

The UI should expose this as something like:

“This thread contains 747 MB of embedded image data. Compact it to restore performance?”

That would turn today’s manual shell surgery into a first-class recovery workflow.

  1. Fail closed per-thread, never globally

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:

Codex opened normally. One thread could not be loaded. [Repair] [Archive] [Show file location] [Export diagnostics]

not:

A JavaScript error occurred in the main process. OK → same dialog forever. Force Quit required.

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. 

  1. Prevent recurrence with policy caps

Codex should also prevent new poisoned sessions from being written. That means caps before persistence, before replay, and before rendering.

For example:

  • Do not persist inline base64 blobs above a tiny threshold.
  • Do not replay megabyte-scale tool outputs into model context.
  • Do not render full image payloads from JSONL.
  • Do not include binary payloads in sidebar previews/search indexes.
  • Do not broadcast full conversation snapshots over IPC for long/completed threads.
  • Warn at 100 MB, compact automatically or strongly prompt at 250–400 MB.

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. 

  1. Add regression tests with pathological fixtures

The upstream fix should include tests that simulate exactly the failure modes users are reporting:

  • 1.6 GB rollout JSONL
  • 750 MB image-heavy rollout
  • 180 MB rollout with 5–7 MB single JSONL lines
  • malformed JSONL line in the middle of a thread
  • missing blob referenced by image_ref
  • 1,300-turn completed thread
  • repeated reconnect of a completed long thread

Success criteria should be:

  • App launches.
  • Sidebar remains usable.
  • Other threads are accessible.
  • Bad thread is isolated.
  • Memory stays bounded.
  • No single JS string or IPC frame grows without limit.
  • Repair/compact can recover the thread.

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.

devinbost · 1 month ago

This script can be pasted into a Mac terminal to identify large files before a crash:

CODEX_HOME="${CODEX_HOME:-$HOME/.codex}"
SESSIONS_DIR="$CODEX_HOME/sessions"
echo "Using Codex home: $CODEX_HOME"
echo
if [ ! -d "$SESSIONS_DIR" ]; then
  echo "No sessions directory found at: $SESSIONS_DIR"
  exit 0
fi
echo "Codex session files larger than 25 MB that the previous script would have moved:"
echo
find "$SESSIONS_DIR" -type f '(' -name 'rollout-*.jsonl' -o -name '*.jsonl' ')' -size +25M -exec stat -f '%z	%N' {} + 2>/dev/null |
sort -nr |
awk -F '\t' '
  function human(bytes) {
    split("B KB MB GB TB", unit)
    size = bytes
    i = 1
    while (size >= 1024 && i < 5) {
      size /= 1024
      i++
    }
    return sprintf("%.2f %s", size, unit[i])
  }
  {
    size = $1
    path = $0
    sub(/^[^\t]*\t/, "", path)
    printf "%12s  %s\n", human(size), path
    total += size
    count += 1
  }
  END {
    print ""
    if (count == 0) {
      print "No matching files larger than 25 MB were found."
    } else {
      printf "Matching files: %d\n", count
      printf "Total size:     %s\n", human(total)
    }
  }
'
devinbost · 1 month ago

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 huge NSData/NSConcreteData byte buffers from Codex app-server thread-event IPC.

Key evidence:

  • Codex parent process was only about 253 MB; SkyComputerUseService was 27 GB.
  • vmmap showed 36.8 GB allocated in the default malloc zone, not mapped files.
  • heap showed 36.7 GB as NSConcreteData (Bytes Storage).
  • The biggest retained buckets were:
  • 231 allocations of 128 MB = 28.9 GB
  • 61 allocations of 64 MB = 3.8 GB
  • 2 allocations of 256 MB = 512 MB
  • sample showed the active queue as CodexAppServerThreadEventObserver.connection, reading via NSFileHandle.read(upToCount:), allocating NSData, then decoding with JSONDecoder.
  • lsof showed the helper connected to the main Codex process through /var/.../T/codex-ipc/ipc-501.sock.
  • The helper was not holding session JSONL files open directly.
  • The binary strings include 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 Data backing stores. The memory also stayed retained after the worker exited, so this is not just temporary buffering.

The one caveat: malloc_history could not recover allocation stacks because the helper was not launched with MallocStackLogging=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.

Necmttn · 28 days ago

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