Codex Desktop can crash or freeze when opening image-heavy sessions because session JSONL embeds base64 image payloads

Open 💬 7 comments Opened Jun 16, 2026 by Howard0401
💡 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)?

26.609.71450

What subscription do you have?

Business, pro

What platform is your computer?

Windows 11

What issue are you seeing?

Summary

Codex Desktop can crash in the Electron main process, or stall the desktop while
loading, when opening a session whose JSONL transcript contains many embedded
image/base64 payloads. The immediate failure observed was
RangeError: Invalid string length inside a main-process stdout/IPC write path,
but the root cause appears to be unbounded session JSON/stdout/IPC payload
construction for records that include large data:image/...base64 strings and
image generation results.

Environment

  • App: Codex Desktop
  • Platform: Windows 11
  • Package version observed in stack trace: OpenAI.Codex_26.609.9530.0_x64__2p2nqsd0c76g0

What steps can reproduce the bug?

Crash

A JavaScript error occurred in the main process

Uncaught Exception:
RangeError: Invalid string length
    at JP.write (...OpenAI.Codex_26.609.9530.0_x64__2p2nqsd0c76g0\app\res...:21)
    at Socket.onStdoutData (...)
    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)

Local Evidence

One affected local transcript had the following properties before repair:

  • JSONL size: about 2.49 GB
  • Line count: about 12k
  • Largest single JSONL record: about 6 MB
  • Image/base64 related records:
  • thousands of data:image/...base64 occurrences in tool output records
  • hundreds of image_generation_end events with multi-megabyte base64 result payloads

An image-only local repair externalized embedded image data and reduced that
transcript to about 25 MB while preserving parseable JSONL, prompts, text
outputs, and normal context.

Representative oversized record shape:

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

Image generation events also store large payloads under fields like:

{
  "type": "response_item",
  "payload": {
    "type": "image_generation_end",
    "result": "<multi-megabyte base64 image>"
  }
}

What is the expected behavior?

Expected Behavior

Opening a previous session should not require materializing all historical
binary/image payloads as one giant JSON/stdout/IPC string. Image-heavy sessions
should remain navigable, with image artifacts loaded lazily or represented by
metadata until explicitly opened.

Actual Behavior

Opening the affected session caused Codex Desktop to crash in the Electron main
process with RangeError: Invalid string length. In related cases, app startup
and session list loading can also become visibly sluggish when the local Codex
state contains multi-GB JSONL/SQLite/session backup data.

In the same local state, after image payload repair, the remaining startup
stutter was correlated with text-heavy session JSONL files around 100-250 MB
each and a multi-GB local log SQLite database. Those files did not contain image
base64 and therefore could not be fixed by image externalization alone. This
points to a broader reader/indexing problem: startup and session list rendering
should not synchronously scan or materialize large historical transcripts or log
stores.

Moving those active session JSONL files out of .codex/sessions is not an
acceptable user-facing workaround for people who rely on the Codex Desktop GUI
as their preserved conversation archive, because it hides conversations from the
sidebar. The fix needs to preserve GUI discoverability while making loading lazy
and bounded.

Proposed Fix

This should be fixed at the session JSON I/O boundary, not only in the renderer UI.

1. Writer-side payload externalization

Do not write data:image/...base64 strings or raw binary-like blobs into session
JSONL. Store image/blob bytes in an artifact store such as a content-addressed
blob directory, and keep JSONL bounded:

{
  "type": "artifact_ref",
  "artifact_id": "sha256-or-uuid",
  "path": "session-artifacts/<thread>/<artifact>.png",
  "mime": "image/png",
  "bytes": 2751234,
  "width": 2048,
  "height": 1152
}

If existing schemas require a string field such as image_url, store a bounded
artifact URI string and maintain a sidecar mapper:

{
  "image_url": "codex-artifact://session-payloads/<thread>/<sha256>.png"
}

2. Reader-side lazy loading and pagination

  • Load session JSONL incrementally.
  • Do not materialize the entire transcript and all image payloads in one string.
  • Elide any single event above a conservative byte threshold.
  • Paginate large tool outputs and old image events.
  • Teach image generation result readers to accept artifact refs, not only raw base64.
  • Build the session list from a compact index rather than reading large JSONL

transcripts during startup.

  • Rotate or compact large local log SQLite stores so startup does not depend on

opening a multi-GB database.

Compatibility note: replacing image_generation_end.payload.result with an
artifact URI in current Codex Desktop builds can cause Invalid image generation
result base64
, because that field is still interpreted as raw base64. Until
renderer support exists, the reader should render a bounded placeholder instead
of sending an artifact URI to a base64 decoder.

3. IPC/stdout hard limits

Add a hard byte cap before main-process stdout/IPC response construction. If a
response exceeds the cap, return a structured truncation record instead of
attempting to allocate a huge string.

4. Migration/repair

On session open or background maintenance:

  • detect legacy transcripts containing large data:image/ strings or raw image base64,
  • move blobs to artifact storage,
  • replace inline image fields with bounded refs or explicit placeholders,
  • preserve the original transcript in a backup location.

Why UI-only truncation is insufficient

The crash happens before a usable UI recovery path: the main process is already
trying to serialize/write oversized data. Renderer-side "show less" behavior
does not protect against unbounded main-process JSON/stdout/IPC writes.

Additional information

Suggested Regression Test

Create a fixture session JSONL containing hundreds of image records with 2-6 MB
base64 payloads. Opening the session should:

  • not crash the Electron main process,
  • keep peak memory bounded,
  • show metadata/placeholders for old image payloads,
  • allow explicit lazy loading of individual artifacts,
  • preserve parseable session history.

Here are my local solution FYI, but I just would like to get the codex runtime fix solution.

https://github.com/Howard0401/codex-session-json-io-guard/tree/main

View original on GitHub ↗

7 Comments

github-actions[bot] contributor · 2 months ago

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

  • #28150
  • #28220
  • #28524
  • #27564
  • #28316

Powered by Codex Action

Necmttn · 2 months ago

The session record contract should make blobs addressable. Store image bytes once in a blob/content store or sidecar keyed by hash, and write transcript records with mime, byte length, hash, model_visible, and source ref.

The UI can lazy-load previews by ref instead of sending base64 over stdout/IPC. Migration can keep old JSONL readable by converting repeated data:image payloads into refs during index build.

---

_Generated with ax._

duofu5566-hash · 1 month ago

Additional reproducible case on Codex Desktop for Windows:

  • App version: 26.707.3748.0
  • Trigger: a cross-thread/task history read against a thread containing an imageGeneration result with inline base64.
  • The history request was bounded by turn count, but the response still included the raw image-generation base64 payload and expanded to approximately 7.1 million tokens.
  • Codex Desktop then showed a main-process exception:
A JavaScript error occurred in the main process

Uncaught Exception:
RangeError: Invalid string length
    at PL.write (...)
    at Socket.onStdoutData (...)
    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)

A metadata-only/lightweight thread listing remained safe. The crash was specifically triggered by serializing historical thread items containing the raw image payload.

Suggested regression coverage: cross-thread history/read APIs should elide or replace imageGeneration.result and other inline binary/base64 fields with bounded metadata, even when tool outputs are nominally excluded. The main-process stdout/IPC boundary should also enforce a hard byte cap and return structured truncation rather than constructing an unbounded string.

cocoichisan · 1 month ago

Additional Windows case with a 5.02 GB image-heavy session

I have a closely matching Windows Desktop case with a larger rollout file and a Chromium crash signature.

Sanitized local audit findings:

  • Affected rollout JSONL size: 5,016,108,657 bytes (about 5.02 GB).
  • Sampling 64 evenly distributed regions found 61/64 inside giant records with no newline in the sampled window.
  • 27/64 sampled regions contained data:image / Base64 markers.
  • Opening/restoring this specific thread repeatedly caused Codex Desktop to terminate and enter recovery behavior.
  • Windows Task Manager previously showed one codex.exe process at approximately 17.2 GB, with total system memory at 97%.
  • A Crashpad dump of about 45 MB was generated.
  • The dump records breakpoint exception 0x80000003 inside chrome.dll; source-level cause is not determined from the dump alone.
  • At one measured startup, Codex used approximately 3.02 GB while Windows still reported approximately 12.88 GB available memory, so this was not simply total system memory already being exhausted before the thread load.
  • Since I stopped opening the affected thread, the repeated crashes have stopped.

The local thread ID, file path, username, and dump path are intentionally redacted.

This appears consistent with the issue's core diagnosis: historical inline image/Base64 payloads produce multi-GB session records, and opening/restoring the session causes unbounded hydration/serialization/rendering pressure in the Desktop/Chromium path. The app should reject, paginate, externalize, or lazily load such payloads rather than crashing or requiring recovery.

hewzhew · 1 month ago

Additional reproducible Windows case on current Desktop build

I reproduced the same task-specific crash on a much smaller, fully parseable local rollout.

Environment

  • Codex Desktop package: 26.721.4979.0 x64
  • Bundled Codex version recorded by the thread: 0.146.0-alpha.3.1
  • OS: Windows 11 Pro, 10.0.26200 (build 26200.8875), x64

Observed behavior

  • One local source=vscode task had a JSONL rollout of 41,482,526 bytes and 3,939 lines.
  • Clicking that task in the Desktop sidebar caused the entire app to terminate after a few seconds, reproducibly.
  • Windows Settings → Apps → Installed apps → Repair allowed the app to launch again, but opening the same task reproduced the crash.
  • The rollout was valid UTF-8; every JSONL line parsed successfully; state_5.sqlite returned PRAGMA quick_check = ok.
  • No matching ChatGPT/Codex crash was present in the Windows Application event log during the recurrence window.

Payload characteristics

The high-confidence trigger was oversized inline image data in tool outputs, not user-message image attachments:

  • 10 oversized function_call_output / custom_tool_call_output records
  • 13 embedded data:image/png;base64,... images
  • Combined size of those 10 JSON records: 31,931,327 bytes (~30.45 MiB)
  • Seven consecutive view_image results each contained one 1848 × 2594 PNG
  • Largest single JSONL record: 5,452,547 bytes

Representative record shape matches the report above:

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

Local isolation / repair evidence

After preserving an untouched SHA-256-verified backup, I:

  1. losslessly extracted the 13 inline images;
  2. replaced only those 10 oversized tool-output payloads with bounded text placeholders;
  3. kept every record and all user/assistant text unchanged.

Result:

  • Before: 41,482,526 bytes
  • After: 9,558,709 bytes
  • Lines before/after: 3,939 / 3,939
  • JSON errors after repair: 0
  • Oversized inline-image tool outputs after repair: 0
  • The current Desktop thread/read path successfully returned the repaired task's recent turns and final answer.

A read-only scan of 83 main local tasks found 16 additional tasks containing tool-output records over 512 KiB with inline image data. This suggests the failure can recur across otherwise normal image/PDF review workflows.

Expected behavior

  • view_image results should be externalized or referenced as artifacts instead of persisted as multi-megabyte Base64 strings.
  • Historical images should be lazy-loaded or represented by bounded placeholders.
  • thread/read / IPC should impose hard per-record and per-response byte caps.
  • A single oversized task should fail in isolation with a recoverable UI message, never terminate the entire Desktop app.
  • Legacy affected rollouts should be migrated without hiding the task from the sidebar.

This also appears related to #35799, which reports background prefetch of an image-heavy rollout crashing the app without explicit navigation.

Privacy note: usernames, local paths, task titles, thread IDs, prompts, project content, images, and raw transcripts are intentionally omitted.

hewzhew · 1 month ago

Follow-up: I isolated a second, independent crash trigger after removing oversized inline image payloads from the affected rollout.

Environment: Codex Desktop on Windows, app package 26.721.4979.0 (Browser Use release logged as 26.721.41059).

Observed sequence:

  1. The affected thread had persisted per-thread Browser Use state containing one active right-panel browser tab, even though rightPanelOpen was false.
  2. Opening the thread completed both thread/read and thread/resume successfully.
  3. The desktop then recreated and attached the persisted Browser Use webview and restored its previous search URL.
  4. About 10 seconds later Electron logged:
  • Recoverable Chromium child process gone ... processType=GPU reason=crashed exitCode=101457950
  • immediately followed by processType=GPU reason=launch-failed exitCode=18

The desktop then exited.

  1. I backed up the state and removed only that thread's thread-browser-tabs-v1:<thread> persisted atom. The rollout, database row, project assignment, and all other global state were left intact.
  2. After restarting, the same thread opened and scrolled normally. Logs showed browserTabId=null, no browser webview creation/attachment, no restored URL, and no new GPU child-process failure. The removed browser state was not recreated.

This was separate from the large-inline-image problem: reducing the rollout from about 41.5 MB to 9.6 MB (while keeping all messages/reasoning and extracting images losslessly) was not sufficient by itself; the crash continued until the stale Browser Use tab state was removed.

Additional checks after the repair:

  • SQLite PRAGMA quick_check: ok
  • rollout JSONL: all 3,939 lines parse successfully
  • backend thread/read: succeeds
  • the historical inline “Used Browser” marker still renders in the transcript, but it is harmless and does not recreate a browser tab or webview

Expected behavior: a hidden/stale Browser Use tab should not be restored as active when reopening a thread, and a Browser Use GPU child-process failure should not terminate the entire desktop app.

shleder · 14 days ago

Your 2.49 GB image-heavy session is a useful stress case because opening it crashes the main process, yet the persisted JSONL remains parseable and a local image-only repair reduced it to 25 MB.

I’m trying to validate whether Codex Rescue’s current diagnostic boundary handles this safely. It is local-first and will not externalize images, rewrite the rollout, or claim to fix the Desktop IPC failure. It reads bounded evidence and stays fail-closed; on this record-size distribution it may report that more manual review is required.

If you kept the untouched pre-repair copy, could you try:

pipx install codex-rescue==0.1.0a3
codex-rescue sessions
codex-rescue doctor --latest

Sanitized output only, please—no JSONL, base64 data, databases, prompts, local paths, or secrets.

https://github.com/shleder/codex-rescue