macOS Desktop: image-heavy thread takes minutes to open because rollout JSONL embeds generated image payloads inline

Open 💬 7 comments Opened May 14, 2026 by ktaletsk

What version of the Codex App are you using?

Codex Desktop 26.506.31421 on macOS. App bundle:

  • CFBundleShortVersionString: 26.506.31421
  • CFBundleVersion: 2620
  • Bundle ID: com.openai.codex
  • Session meta in rollout: cli_version: 0.130.0-alpha.5, originator: Codex Desktop

What platform is your computer?

macOS 26.5 (25F71), Apple Silicon / arm64.

What issue are you seeing?

Opening one Codex Desktop thread for a local repo with many generated images made the Desktop app appear to crash or become unresponsive. It eventually opened after several minutes, but the thread was heavy enough that normal Desktop use became painful.

Local diagnosis shows the session trace stores full image bytes inline, even though the generated image files are also written separately under ~/.codex/generated_images/.

Measured local data for the affected thread:

  • ~/.codex/sessions/2026/05/11/rollout-<thread-id>.jsonl: 747 MB
  • JSONL record count: 2,164
  • Matching ~/.codex/generated_images/<thread-id>/: 82 PNG files, 240 MB
  • 82 generated images appear to be stored twice in the JSONL: once as event_msg / payload.type == image_generation_end, and once as response_item / payload.type == image_generation_call
  • 164 JSONL records contain multi-megabyte payload.result base64 PNG strings
  • 35 additional function_call_output records contained embedded input_image.image_url: data:image/...;base64 payloads
  • 191 JSONL lines were larger than 1 MB; the oversized lines accounted for most of the rollout file

Manual recovery confirmed the root cause. Redacting only embedded image payloads while preserving all JSONL records and metadata reduced the rollout file from 747 MB to 2.3 MB:

  • replace payload.result for image_generation_end / image_generation_call with a short placeholder
  • replace nested image_url values beginning with data:image/ with a short placeholder
  • keep saved_path, call ids, prompts, timestamps, and all other metadata intact
  • validate JSONL with jq -e .

After this trim, the file still had the same 2,164 records and no JSONL line above 1 MB.

This looks related to, but not exactly the same observed platform, as:

I am filing this as a macOS Desktop datapoint with exact local measurements. Feel free to close as duplicate if the intended fix is tracked elsewhere.

Steps to reproduce

  1. Use Codex Desktop on macOS.
  2. In one thread, generate dozens of images, enough to create many records with payload.type == image_generation_end and payload.type == image_generation_call.
  3. Confirm the rollout JSONL grows into hundreds of MB because generated images are embedded as base64 in payload.result while also being saved under ~/.codex/generated_images/<thread-id>/.
  4. Close/reopen Codex Desktop or select that thread.
  5. Observe that opening the thread can freeze/hang the Desktop UI for minutes or make it appear to crash.

Expected behavior

Codex Desktop should stay responsive when opening image-heavy threads. Generated image bytes should not be duplicated inline in the session rollout if they are already stored as files, or the loader should lazy-load/stream/skip oversized binary payloads safely.

At minimum, Desktop should detect oversized image-heavy sessions, show a clear warning, and provide a built-in way to compact/trim embedded images without forcing users to manually edit ~/.codex/sessions.

Actual behavior

The Desktop app attempts to load/hydrate a hundreds-of-MB rollout file with many inline base64 image payloads, causing the UI to become unresponsive for minutes when opening the thread.

Privacy note

I intentionally omitted the raw rollout file, prompts, local username, full thread id, generated images, and repo contents from this public report. The important reproducible signal is the data shape and sizes above.

View original on GitHub ↗

7 Comments

ktaletsk · 3 months ago

Additional local recovery note: replacing embedded image bytes with arbitrary placeholder strings is not enough for a compacted thread to continue. Codex replay/API validation still treats prior tool outputs as typed image values.

In this affected thread, changing input_image.image_url values from [trimmed ...] placeholders caused a 400 on the next send:

{
  "type": "invalid_request_error",
  "code": "invalid_value",
  "message": "Invalid 'input[19].output[0].image_url'. Expected a valid URL, but got a value with an invalid format.",
  "param": "input[19].output[0].image_url"
}

A schema-valid compact form worked better locally: replace old input_image.image_url data URLs with a tiny valid data:image/png;base64,... URL, and replace image_generation_end / image_generation_call result values with the tiny PNG's raw base64. The rollout stayed about 2.3 MB, but no longer contained invalid placeholder image URLs.

For an official compaction feature, it would be useful if Codex either omits old binary image tool outputs from replay in a schema-aware way, or substitutes typed minimal image references that still satisfy Responses API validation.

ktaletsk · 3 months ago

Follow-up correction from local recovery: the tiny 1x1 PNG substitute was still rejected on replay with Invalid image in your last message.

The compact shape that currently avoids local replay image validation is stricter:

  • convert prior function_call_output.output values that contain input_image items into text-only outputs such as [omitted prior image tool output from compacted Codex session]
  • keep image_generation_end / image_generation_call result fields image-shaped, but use a real valid 64x64 PNG base64 substitute instead of a 1x1 image
  • remove the failed retry turn appended after the validation error

With that, the rollout remains compact (~4.0 MB for this thread), valid JSONL, has no input_image, no image_url, no data:image fields, and no JSONL line above 1 MB.

thisislvca · 1 month ago

Adding a newer generated-image datapoint that matches this issue closely. Full root-crash details are in #22004: https://github.com/openai/codex/issues/22004#issuecomment-4887099344

Short version:

  • Codex Desktop macOS 26.623.101652 (CFBundleVersion=4674), macOS 15.7.7, arm64
  • One image-generation-heavy rollout reached 1,165,931,575 bytes with only 3,240 JSONL records
  • 179 generated PNGs existed as separate artifacts, but the rollout also had 358 inline image_generation_end / image_generation_call result fields totaling 1,138,720,392 bytes
  • Opening/reading the thread produced the same failure class: RangeError: Invalid string length and a logged thread/read JSON line of 536,871,005 bytes

The local repair shape that stayed JSONL-valid and schema-friendlier was:

  • replace image_generation_end / image_generation_call result values with a small valid 64x64 PNG base64 string, not arbitrary text
  • convert old function_call_output.output values containing input_image / data:image into text-only omitted-output strings
  • preserve prompts, ids, timestamps, and saved_path

That reduced the rollout to 6,096,679 bytes, same 3,240 records, max line 84,047 bytes, zero raw data:image records, and zero invalid image-generation result records.

jseook11 · 1 month ago

Adding a current macOS storage-growth datapoint.

Environment:

  • Codex Desktop: 26.715.31925 (build 5551)
  • Bundled Codex CLI: 0.145.0-alpha.18
  • macOS 26.5.2, Apple Silicon arm64

Aggregate measurements:

  • 2026-07-17: 2.380 GiB of rollout JSONL; 2.281 GiB (95.85%) was in 35 image-generation sessions
  • 2026-07-19: 2.033 GiB total; 1.915 GiB (94.23%) was in 72 image-generation sessions
  • Combined: 4.197 GiB of image-session rollouts in two days, accounting for 95.10% of those days' session storage
  • Top-level image_generation_end events: 390 on July 17 and 841 on July 19

Representative rollout:

  • File size: 123.44 MiB, 4,280 JSONL records
  • 18 image_generation_end results totaling 36,851,976 characters
  • All 18 already had saved_path values
  • 144 inline data:image URLs, 107 unique, totaling 67,695,168 characters
  • Embedded image strings accounted for approximately 99.70 MiB / 80.77% of the complete rollout
  • Separate generated-image store currently contains 152 files / 198 MiB

This shows a storage problem even before one individual rollout reaches the sizes that cause UI hangs. Generated images are saved as files but substantial image payloads are still retained inline in durable session JSONL, while tool outputs and compaction can amplify the footprint.

The current open-source image-generation path saves the image and still retains result.clone() in ImageGenerationItem, the legacy event, and the generated tool output. The existing thread-resume redaction is response-only and explicitly leaves persisted rollout history unchanged.

Expected:

  • Persist generated image bytes once
  • Store a content-addressed artifact/file reference plus metadata in rollout history
  • Avoid carrying raw image bytes into compacted replacement history
  • Provide built-in cleanup/repair for legacy image-heavy sessions
dd3ok · 1 month ago

Adding a Windows Codex Desktop datapoint that reproduces the same durable-storage amplification.

Environment

  • Codex Desktop: 26.721.4979.0
  • Platform: Windows 11 x64
  • Workload: one long-running image-generation thread with three child threads

Measurements

  • Separate generated-image store: 286 PNG files / 476.6 MiB
  • Unique PNG SHA-256 hashes: 286
  • Exact duplicate PNG files: 0
  • Root rollout: 2,086.2 MiB
  • Child rollouts: 989.5 MiB, 986.6 MiB, and 525.3 MiB
  • Total rollout storage for the thread family: 4,587.7 MiB (4.48 GiB)
  • Estimated inline base64 image data across those rollouts: approximately 2,027.5 MiB (1.98 GiB)

The rollout family was about 9.6x the size of the separately stored PNG artifacts. This is not intended as a pure image-duplication ratio because the rollouts also contain text and tool records, but it shows the resulting storage amplification.

The PNG store itself contained no exact duplicate files. The large footprint instead appears in durable rollout records, where image payloads are retained inline in image-generation events, tool outputs, and compacted history.

The child-thread amplification portion of this observation is also relevant to #34268.

Proposed direction

  • Persist generated image bytes once.
  • Store a stable artifact reference, content hash, and metadata in rollout history.
  • Do not copy raw image bytes into compacted replacement history.
  • Preserve backward-compatible loading for existing image-heavy rollouts.
  • Provide a supported cleanup or migration path for legacy sessions.

Privacy

This analysis used file sizes, record types, encoded lengths, and SHA-256 hashes only. I am not attaching raw rollouts, prompts, image contents, local paths, usernames, project names, or thread IDs.

shleder · 14 days ago

The 747 MB rollout with 164 multi-megabyte generated-image records is a very strong real-world oversized-payload case, especially because the thread became practically unusable on reopen.

I’m testing Codex Rescue’s bounded diagnosis against this exact persisted shape. It is local-first, does not decode or rewrite inline images, and leaves the original rollout byte-for-byte untouched. It also will not perform the schema substitutions you tested or claim to fix Desktop loading; verification remains fail-closed.

If you still have an untouched backup of the affected session, could you try:

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

Sanitized diagnostic output is sufficient—please don’t publish the rollout, images, database, prompts, paths, or secrets.

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

indada · 9 days ago

Reproduced on macOS with the unified ChatGPT/Codex desktop app 26.810.52044 (bundled codex-cli 0.148.0-alpha.9).

This looks like a current-version continuation failure related to this issue:

  • The affected local rollout JSONL is approximately 97 MB.
  • The thread contains 12 image-generation events/calls and about 4,906,769 cumulative processed tokens.
  • The old thread opens, but sending a follow-up leaves the UI on “thinking” indefinitely and later shows a misleading network reconnect state.
  • New threads in the same app/account/project work normally.
  • Restarting the desktop app does not recover the affected thread.

Sanitized log sequence:

turn/start ... errorCode=null
Conversation state not found
Received turn/started for unknown conversation
Received turn/completed for unknown conversation
maybe_resume_success ... latestTurnStatus=interrupted markedStreaming=true
chatgpt_pubsub_transport_closed
chatgpt_pubsub_reconnect_scheduled
chatgpt_pubsub_transport_opened

The turn/start request succeeds and other app-server/config/auth requests continue succeeding, so this does not appear to be a real network outage. The failure seems to be a mismatch between the persisted thread state and the renderer/app-server event routing, triggered or amplified by the image-heavy rollout.

Expected: image-heavy historical threads should remain continuable, or the app should compact/lazy-load old image payloads and show a precise recovery error.

Actual: the existing thread cannot produce further assistant/tool output, while fresh threads work.

Privacy note: I intentionally omitted the raw rollout, thread ID, prompts, generated images, repository contents, username, and local paths.