Skip to content

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

Open

Description

@Howard0401

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

Activity

  1. added
    appIssues related to the Codex desktop app
    on Jun 16, 2026
  2. added
    bugSomething isn't working
    windows-osIssues related to Codex on Windows systems
    sessionIssues involving session (thread) management, resuming, forking, naming, archiving
    on Jun 16, 2026
  3. Necmttn commented on Jun 21, 2026

    @Necmttn

    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.

  4. duofu5566-hash commented on Jul 15, 2026

    @duofu5566-hash

    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.

  5. cocoichisan commented on Jul 28, 2026

    @cocoichisan

    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.

  6. hewzhew commented on Jul 29, 2026

    @hewzhew

    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.

  7. hewzhew commented on Jul 29, 2026

    @hewzhew

    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.
    5. 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.
    6. 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.

  8. Polvoo21 commented on Sep 2, 2026

    @Polvoo21

    Additional Windows case: 8.41 GiB rollout, 64 s thread/read, then renderer crash

    This is a content-safe report from another reproducible Windows installation. It strongly matches the unbounded inline-image/history hydration problem described in this issue.

    Environment

    • OS: Windows 11 Pro 10.0.22621, x64
    • Physical RAM: 63.9 GiB
    • App package at the time of the crash: OpenAI.Codex_26.825.6671.0_x64
    • Bundled app-server reported in the crash log: 0.151.0-alpha.7.2
    • The app auto-updated after the incident and currently reports 26.831.2377.0 with codex-cli 0.152.1
    • No Playwright process, Google Chrome activity, or system-wide memory exhaustion was present during the incident.

    Sanitized affected-rollout measurements

    • File size: 9,027,292,196 bytes (8.41 GiB)
    • JSONL lines: 48,847
    • Average record size: approximately 180.5 KiB
    • Lines matching "type":"custom_tool_call_output": 8,335
    • Lines matching data:image: 2,424
    • Lines matching image_url: 1,775
    • Lines matching "type":"image": 364
    • Several other active rollouts on the same installation are also in the roughly 4-9 GiB range.

    No task title, thread ID, prompt, image, local project path, or raw transcript is included here.

    Exact failure sequence from Codex Desktop logs

    All timestamps below are UTC on 2026-09-02:

    1. The image/tool-heavy thread was opened/restored.

    2. thread/resume took 33,289 ms.

    3. A thread/turns/list request took 37,462 ms.

    4. thread/read completed after 63,955 ms; a concurrent thread/goal/get took 48,095 ms.

    5. Four seconds later, at 10:41:23Z, Codex Crashpad wrote a sidecar with:

      {"capture_kind":"crash","osarch":"x86_64","ptype":"renderer"}
    6. The main-process log then reported that the renderer/browser-use route window was no longer live, followed by write EPIPE at 10:41:48Z.

    7. Codex Desktop relaunched at approximately 10:41:51Z.

    The Windows Application log had no corresponding Application Error, Hang, WER, or resource-exhaustion event. Shortly after the incident, Windows still had approximately 37-40 GiB of free physical RAM. This makes system-wide OOM unlikely, although a renderer/V8/IPC allocation limit remains plausible.

    Crashpad also shows this is recurrent rather than a one-off: from 2026-08-31 through this incident it recorded nine Codex crashes on this installation, eight with ptype=renderer and one with ptype=browser.

    User-visible behavior

    • Clicking a large chat leaves the UI loading for about one minute, matching the measured thread/read latency.
    • The UI can crash shortly after the history read completes and then automatically restart.
    • Smaller but still multi-gigabyte histories show shorter, size-correlated delays.
    • This can happen without any Chrome/Browser Use/Playwright activity.

    Expected behavior / why a new chat is not an acceptable workaround

    Long-running project chats are intended to remain usable and will continue accumulating tool results and images. Users should not have to abandon working history whenever the rollout grows.

    Codex should:

    1. externalize and content-deduplicate binary/image payloads instead of persisting inline Base64 repeatedly;
    2. ensure compaction does not retain or duplicate superseded image/tool payloads;
    3. index, paginate, and lazily hydrate history rather than parsing/materializing an entire multi-gigabyte rollout for thread/read or renderer IPC;
    4. enforce hard per-record and per-response byte limits with recoverable placeholders/truncation;
    5. migrate existing oversized rollouts without removing chats from the sidebar or losing their text history;
    6. isolate one oversized thread so it cannot terminate the entire desktop renderer.

    The raw session and full logs contain private project content and are therefore not suitable for a public attachment. The sanitized timing and aggregate measurements above are directly derived from the local Codex logs and rollout file.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    appIssues related to the Codex desktop appbugSomething isn't workingperformancesessionIssues involving session (thread) management, resuming, forking, naming, archivingwindows-osIssues related to Codex on Windows systems

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions