macOS desktop startup crash when a rollout JSONL contains a huge single line

Open 💬 2 comments Opened Mar 21, 2026 by kaonis

What version of Codex is running?

Codex.app 26.318.11754 (CFBundleVersion 1100)
Installed via Homebrew cask: brew install --cask codex-app

Which model were you using?

Unknown / not relevant. This happens during desktop app startup before normal use.

What platform is your computer?

macOS 26.3.1 (25D2128) on arm64

What steps can reproduce the bug?

  1. Have a rollout-*.jsonl file under ~/.codex/sessions/... with one extremely large line.
  2. Launch the Codex macOS desktop app.
  3. The app fails during startup with a main-process JavaScript exception.

Observed dialog / stack:

A JavaScript error occurred in the main process
Uncaught Exception:
RangeError: Invalid string length
    at [_normalWrite] [as _normalWrite] (node:internal/readline/interface:652:31)
    at ReadStream.ondata (node:internal/readline/interface:263:23)
    at ReadStream.emit (node:events:508:28)
    at addChunk (node:internal/streams/readable:559:12)
    at readableAddChunkPushByteMode (node:internal/streams/readable:510:3)
    at Readable.push (node:internal/streams/readable:390:5)
    at node:internal/fs/streams:292:14
    at FSReqCallback.wrapper [as oncomplete] (node:fs:670:5)

What did you expect to happen?

Startup should not crash because of a malformed or oversized rollout log.
The app should skip the file, cap line size, or parse it using a streaming approach that does not let readline accumulate an arbitrarily large line in memory.

Additional context

I traced the crash to desktop startup scanning rollout logs for rollout metrics.
The shipped app bundle appears to read rollout-*.jsonl via readline.createInterface(...) during startup.

The file that triggered it on my machine was:

~/.codex/sessions/2026/03/21/rollout-2026-03-21T21-11-23-019d11cf-2c7f-7341-8228-c44709509ba4.jsonl

Its characteristics were:

  • file size: 539303205 bytes
  • line count: 203
  • largest line: about 538928628 bytes on line 17

I independently reproduced the exact same RangeError outside the app by running Node readline against that file.

Workaround

Moving the bad rollout file out of ~/.codex/sessions allowed Codex Desktop to start normally again.

Example workaround:

mkdir -p ~/.codex/quarantine
mv ~/.codex/sessions/2026/03/21/rollout-2026-03-21T21-11-23-019d11cf-2c7f-7341-8228-c44709509ba4.jsonl \
   ~/.codex/quarantine/rollout-2026-03-21T21-11-23-019d11cf-2c7f-7341-8228-c44709509ba4.jsonl.corrupt

Suggested fix

Any of these would avoid taking down the app on startup:

  • refuse to parse rollout lines above a size cap
  • skip malformed / oversized rollout logs during startup metrics aggregation
  • avoid readline for untrusted large JSONL and use a bounded streaming parser
  • isolate rollout metrics parsing so a single bad file cannot crash the main process

View original on GitHub ↗

2 Comments

ignatremizov · 4 months ago

Reproduced on macOS Intel with the current desktop app, and I have an additional signal about how the bad file was produced.

Environment:

  • Codex.app 26.415.21839 (CFBundleVersion 1763)
  • Electron 41.2.0
  • Node v24.14.0
  • bundled buffer.constants.MAX_STRING_LENGTH = 536870888
  • macOS 15.7.4 on Intel (x64)

Observed startup error matches this issue:

A JavaScript error occurred in the main process
Uncaught Exception:
RangeError: Invalid string length
    at [_normalWrite] [as _normalWrite] (node:internal/readline/interface:652:31)
    at ReadStream.ondata (node:internal/readline/interface:263:23)
    at ReadStream.emit (node:events:508:28)
    at addChunk (node:internal/streams/readable:559:12)
    at readableAddChunkPushByteMode (node:internal/streams/readable:510:3)
    at Readable.push (node:internal/streams/readable:390:5)
    at node:internal/fs/streams:292:14
    at FSReqCallback.wrapper [as oncomplete] (node:fs:670:5)

Local triggering file:

~/.codex/sessions/2026/04/10/rollout-2026-04-10T01-16-41-019d7451-a844-7fb2-a0f7-6e57c281f961.jsonl

Its characteristics:

  • file size: 565M
  • line count: 542
  • largest line: 591055538 bytes on line 15

That largest line is above Node/V8's max string length on this build (536870888), so readline blows up when startup scans the file.

Additional useful signal:

  • the oversized line does not appear to come from user input
  • it appears to be a massively bloated tool-output record generated during a session that was searching older .codex/sessions/YYYY/MM/DD/*.jsonl logs
  • the session context was investigating a recurring macOS codesign / NordVPN helper / keychain-related issue, and the huge record seems to be an aggregated search/output payload from that log-inspection task

So this looks broader than “one malformed rollout line from model output”:

  • desktop startup should be robust to arbitrarily large tool-output records inside rollout JSONL
  • the producer side may also need bounds / truncation / chunking for tool outputs written into rollout logs
shleder · 6 days ago

The RangeError: Invalid string length in node's readline is the tell — readline materializes each line as a single JS string, and one oversized line (almost always a base64-inlined image or a giant tool output) exceeds the string length limit and crashes the main process at startup, before the app can even decide to skip the file. The rollout isn't malformed per se; it's that one record is too large for the line-based reader.

The safe handling is to not read line-by-line into memory: cap line size, stream-parse with a bounded buffer, or skip-and-quarantine the oversized record so startup survives. For anyone blocked, the immediate question is which record is oversized and how much of the file it accounts for — that determines whether the session is salvageable minus that one payload.

Full disclosure: I maintain codex-rescue (https://github.com/shleder/codex-rescue). codex-rescue doctor --latest classifies oversized/malformed records and storage profiles per-record byte distribution read-only, so you can pinpoint the single huge line and quantify how much of the rollout it dominates without loading the whole file into memory. That tells you whether stripping that one payload recovers the rest of the transcript.