Desktop compaction repeatedly embeds full image base64 in compacted checkpoints

Open 💬 13 comments Opened May 18, 2026 by SimpleZion
💡 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?

Codex Desktop on Windows:

  • App package: OpenAI.Codex
  • Version: 26.513.4821.0
  • Package: OpenAI.Codex_26.513.4821.0_x64__2p2nqsd0c76g0

One affected rollout also recorded cli_version: 0.119.0-alpha.28.

What platform is your computer?

Windows x64.

What issue are you seeing?

Codex Desktop context compaction appears to repeatedly persist full inline image payloads inside compacted checkpoints, especially under payload.replacement_history.

This makes long image-heavy threads keep growing even after automatic compaction. It also keeps the "background information window" / context usage high after compaction because the new compacted checkpoint can still contain the previous screenshots as base64 data.

This looks related to #18629 and #22603, but this report is focused on the compaction layer: old compacted checkpoints and the latest compacted.payload.replacement_history repeatedly contain full image payloads instead of deduplicated references, lightweight placeholders, or a single latest compacted state.

Local evidence

I inspected local saved session JSONL files under the Codex home directory. I am intentionally omitting raw session contents, local paths, screenshots, cookies, thread text, and user data.

Affected thread A

Before repair:

  • Rollout JSONL size: 904.9 MB
  • JSONL records: 40,378
  • compacted records: 128
  • Total bytes in compacted records: 770.9 MB
  • Lines containing data:image: 315
  • Total bytes in lines containing data:image: 822.5 MB
  • Total data:image references: 1,787
  • Latest compacted record size: 7.66 MB
  • Latest compacted image refs: 15
  • Latest compacted.payload.replacement_history length: 185
  • Latest compacted visible string chars before image removal: about 7,954,167
  • Latest compacted contained 15 image parts, plus a copied developer/plugin instruction block and an encrypted compaction blob.

After a local schema-aware cleanup that kept the latest compacted checkpoint but removed inline image payloads and old duplicate compacted checkpoints:

  • compacted records: 1
  • data:image refs: 0
  • Latest compacted size: about 316 KB
  • Latest replacement_history length remained 185
  • Latest visible string chars after image removal: about 264,828

This is the important signal: the useful compacted text was much smaller than the full checkpoint because prior screenshots had been embedded into replacement_history.

Affected thread B

Another long thread repeatedly regenerated large compacted checkpoints:

  • One snapshot: rollout size 430 MB
  • compacted records: 14
  • Total bytes in compacted records: 117.6 MB
  • Latest compacted record: 9.02 MB
  • Latest compacted image refs: 24
  • The Codex UI showed the background information window still around 54% / 139k tokens after compaction.
  • Local token-count records around the same time showed a post-compaction context total around 138,789 / 258,400.

After removing old compacted records and image payloads from the latest compacted checkpoint, the same thread became much smaller and the latest compacted record was under 100 KB. However, if the thread continued running, Codex could generate new compacted records that reintroduced the same image payloads.

Why this is a product-level problem

Users can manually edit JSONL as a workaround, but it is risky. During local recovery, replacing large base64-looking strings too broadly can damage fields such as encrypted_content, which then causes resume/reconnect failures like:

{
  "type": "error",
  "error": {
    "type": "invalid_request_error",
    "code": "invalid_encrypted_content",
    "message": "The encrypted content [...] could not be verified. Reason: Encrypted content could not be decrypted or parsed."
  },
  "status": 400
}

So the durable fix should not require users to manually mutate saved session JSONL. Codex should compact or repair these sessions in a schema-aware way.

Steps to reproduce

  1. Use Codex Desktop in a long-running thread.
  2. Use image-producing workflows repeatedly, such as screenshots, browser/computer-use images, image attachments, or other tool outputs that produce input_image / data:image/...;base64,... payloads.
  3. Let Codex automatically compact the context several times.
  4. Inspect the saved rollout JSONL.
  5. Observe many compacted records and a latest compacted.payload.replacement_history that still contains full image payloads.
  6. Continue the same thread; observe that automatic compaction can create more large compacted records rather than converging to a lightweight latest summary.

Expected behavior

Context compaction should not repeatedly embed full image bytes in compacted.payload.replacement_history.

Instead, Codex should:

  • store image blobs out-of-line and keep only lightweight references in replayable history
  • deduplicate image payloads across records and compacted checkpoints
  • avoid retaining old compacted checkpoints once a newer compacted checkpoint supersedes them, or keep them out of the replay/hydration path
  • replace old image-heavy tool outputs with schema-valid lightweight placeholders during compaction
  • keep encrypted_content opaque or remove whole reasoning records in a schema-aware way during any repair path
  • provide a built-in "repair / strip old media payloads" action for already affected sessions

Actual behavior

compacted checkpoints can contain full inline image base64 payloads. Old compacted checkpoints accumulate, and the latest compacted checkpoint can still contain many prior screenshots in replacement_history. The thread remains slow/heavy after compaction and can continue to re-grow as new compacted checkpoints are written.

Related issues

  • #18629
  • #22603
  • #11845

Privacy note

I am not attaching the raw rollout JSONL because it contains private prompts, local paths, screenshots, and authentication material. The report above includes only sanitized structural measurements and field/type counts.

View original on GitHub ↗

13 Comments

github-actions[bot] contributor · 3 months ago

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

  • #22603
  • #22004

Powered by Codex Action

banteg · 2 months ago

Additional local evidence from a much larger affected rollout. This sample is useful because it separates the disk-growth mechanism from the raw private transcript contents.

Sanitized chart/data:

!11GB rollout compaction growth

Rollout-level measurements

One local rollout JSONL from 2026-04-24 grew to about 11.36 GiB by the last compaction point.

  • JSONL lines: 104,082
  • compacted records: 287
  • Bytes in compacted records: 11,465,609,653 bytes, about 10.68 GiB
  • Largest single compacted record: 54,639,204 bytes, about 52.1 MiB
  • Cumulative total rollout size at compaction 287: about 11.36 GiB
  • Cumulative compacted-record contribution at compaction 287: about 10.68 GiB

So in this sample, compacted checkpoints alone account for nearly all of the rollout file size.

The records are not opaque

The large compacted records are normal structured JSON:

  • top-level type: "compacted"
  • payload.message
  • payload.replacement_history
  • replacement_history is an array of response items

The largest compacted record contained exactly one replacement_history key in the whole JSON record, so this was not recursive self-embedding of prior replacement_history blobs.

What caused the amplification

The amplification came from repeatedly snapshotting inline image payloads that remained inside replacement_history.

Across all 287 compacted records in this rollout:

  • Records with image parts: 286 / 287
  • Total copied input_image parts across compacted snapshots: 9,173
  • Total JSON bytes for those copied image parts: 11,441,321,095 bytes, about 10.66 GiB
  • Total bytes in the image_url strings alone: 11,440,834,926 bytes, about 10.66 GiB
  • Unique image parts after hashing each input_image JSON object: 43
  • Unique image payload footprint: 53,765,897 bytes, about 51.3 MiB
  • Text parts in the same compacted histories: 53,929, but only about 13.9 MiB total
  • compaction items inside histories: about 6.0 MiB total

That means about 51 MiB of unique image payloads were repeatedly copied into compacted checkpoints until they contributed about 10.66 GiB on disk, roughly a ~213x amplification in this rollout.

The largest compacted record showed the same pattern locally:

  • record size: 54,639,204 bytes
  • replacement_history items: 155
  • input_image parts in that record: 44
  • JSON bytes for image parts in that one record: 54,536,192 bytes

The first inspected image part had keys type, image_url, and detail; the image_url field alone was about 1.29 MB.

Interpretation

This looks like append-only snapshot amplification:

  1. Compaction replaces the live context with a new history.
  2. The rollout persists the compacted item with a full replacement_history snapshot.
  3. Inline image image_url payloads remain in that replacement history.
  4. Every subsequent compaction writes another full snapshot containing the same large image payloads.
  5. Old compacted records remain in the append-only rollout, so disk use grows by the sum of all snapshots rather than converging to the latest compacted state.

This is not merely a long-session transcript being large. The same 43 unique image payloads were copied thousands of times across compacted records.

The current code path appears to match this behavior: compaction writes CompactedItem { replacement_history: Some(items.clone()), ... }, and the rollout recorder serializes each rollout item as a new JSONL line. Compression would reduce disk footprint, but it would not address the replay/hydration cost or the repeated serialized image payloads unless the image bytes are deduplicated, moved out-of-line, or replaced with schema-valid lightweight references/placeholders before persistence.

xmt0114 · 1 month ago

I can confirm a severe instance of this issue on Codex Desktop for Windows, with a long-running UI/debugging thread that accumulated many screenshots and then entered a repeated auto-compaction loop.

Environment:

  • OpenAI Codex Desktop: 26.623.5546.0
  • app-server CLI: codex-cli 0.142.3
  • Windows
  • Long-running thread with repeated screenshots and many automatic compactions

Structural evidence from the affected rollout JSONL before mitigation:

  • rollout size: ~2.05 GB
  • JSONL lines: 3215
  • compacted records: 52
  • total compacted bytes: ~2041.747 MB
  • latest compacted record: ~39.669 MB
  • latest payload.replacement_history items: 1678
  • latest replacement history retained input_image parts: 145
  • image JSON inside latest replacement history: ~39.216 MB
  • image_url chars in latest replacement history: 41,113,066

Observed behavior:

  • The UI repeatedly auto-compacts after only a little work.
  • After compaction, context remains very high because the retained replacement history still contains the same inline images.
  • Every subsequent compaction writes another full copy of those retained inline base64 image payloads, so the session file grows quickly.

Manual mitigation that restored usability for this thread:

  • Backed up the rollout.
  • Kept only the latest 10 compacted checkpoints plus suffix history.
  • Parsed JSONL schema-aware.
  • Modified only JSON input_image content items by replacing them with short input_text placeholders.
  • Preserved message order, text content, compacted records, thread metadata, and encrypted content.
  • Result: active rollout shrank from ~2051 MB to ~5.97 MB; compacted records kept: 10; input_image count: 0; JSON parse errors: 0.

Expected behavior / possible product fixes:

  • Compaction should account for retained image payload byte size / image token cost, not only text token budget.
  • Old images in replacement history should be summarized, externalized, deduplicated, truncated by age/count/byte budget, or replaced with explicit lightweight placeholders.
  • Compacted checkpoints should not repeatedly persist the same full inline image payloads.
  • Codex Desktop should expose a safe built-in repair action such as "strip historical images from compacted history" for affected sessions.
wgu9 · 1 month ago

Additional current-main source trace that seems to match the reported amplification:

  • CompactedItem is schema-backed as replacement_history: Option<Vec<ResponseItem>> in codex-rs/protocol/src/protocol.rs.
  • Both remote compaction paths install a full cloned replacement history: codex-rs/core/src/compact_remote.rs builds CompactedItem { replacement_history: Some(new_history.clone()), ... }, and codex-rs/core/src/compact_remote_v2.rs does the same for v2.
  • The final persistence boundary is CodexThread::replace_compacted_history in codex-rs/core/src/session/mod.rs: after item-id assignment it overwrites the compacted item with replacement_history: Some(items.clone()) and then appends RolloutItem::Compacted(compacted_item) to the rollout.

So the durable record is intentionally a full structured snapshot of the installed post-compaction history. If any retained ResponseItem still contains inline input_image.image_url data, every later compaction writes another full copy into a new append-only JSONL line. That matches the measurements above: the bloat is not recursive replacement_history nesting; it is repeated full snapshots of retained inline image payloads.

Suggested fix boundary:

  • Keep the live model-context semantics separate from the persisted rollout snapshot shape.
  • Before persisting CompactedItem.replacement_history, sanitize or externalize large image payloads inside the retained ResponseItems. Options: content-addressed image blobs plus lightweight refs, explicit historical-image placeholders, or a byte/count/age retention budget for image parts.
  • Apply the same boundary in both compact_remote and compact_remote_v2, ideally at or just before replace_compacted_history, so local/remote/v2 behavior cannot diverge.

Regression shape:

  • Build a synthetic thread with a few input_image data URLs.
  • Trigger two compactions.
  • Assert the second rollout's compacted record does not contain repeated full data URLs from the first retained image set, and that resume/reconstruction still produces a schema-valid history.
ignatremizov · 1 month ago

Additional reproduction plus a proposed smallest-stage implementation spec. This is the same thread analyzed in #33493, viewed specifically at the rollout persistence boundary.

Additional storage evidence

A sanitized structural scan of one long-lived image-heavy rollout produced:

| Metric | Observed value |
| --- | ---: |
| Total rollout bytes | 2,873,784,216 |
| compacted records | 54 |
| Bytes in compacted records | 2,731,786,825 |
| Share of rollout bytes in compacted records | approximately 95.1% |
| Latest compacted replacement-history items | 211 |
| Latest retained images | 142 |
| Latest retained image/data-URL characters | 111,563,732 |
| Latest retained text characters | 56,733 |

The latest checkpoint was therefore approximately 111.7 MB of serialized replacement history, almost entirely inline image URLs. The rollout is not recursively embedding prior replacement_history objects. It is append-only snapshot amplification: each compaction persists another full snapshot containing the same surviving media.

All 142 images in the latest checkpoint were already surrounded by local-image text wrappers containing their source paths. The raw data URLs can therefore be removed while retaining cheap, model-callable references for view_image.

No raw rollout is attached because it contains private prompts, paths, tool output, and images. These measurements were computed from record types and structural field lengths without printing content.

Current upstream persistence path

Local compaction v2 constructs CompactedItem with a clone of the newly installed history:

https://github.com/openai/codex/blob/0fb559f0f6e231a88ac02ea002d3ecd248e2b515/codex-rs/core/src/compact_remote_v2.rs#L285-L319

replace_compacted_history then deliberately overwrites replacement_history with another clone of the exact post-ID-assignment live history and appends it as a RolloutItem::Compacted record:

https://github.com/openai/codex/blob/0fb559f0f6e231a88ac02ea002d3ecd248e2b515/codex-rs/core/src/session/mod.rs#L3021-L3051

The persisted schema stores that snapshot directly as Option<Vec<ResponseItem>>:

https://github.com/openai/codex/blob/0fb559f0f6e231a88ac02ea002d3ecd248e2b515/codex-rs/protocol/src/protocol.rs#L3232-L3248

This means a persistence-only filter applied after history installation would make replay/resume diverge from the live context. The safer fix boundary is earlier: sanitize/bound the replacement history before it becomes both live state and the persisted checkpoint.

Historic resume/fork reconstruction currently restores the latest surviving replacement history exactly:

https://github.com/openai/codex/blob/0fb559f0f6e231a88ac02ea002d3ecd248e2b515/codex-rs/core/src/session/rollout_reconstruction.rs#L113-L185

https://github.com/openai/codex/blob/0fb559f0f6e231a88ac02ea002d3ecd248e2b515/codex-rs/core/src/session/rollout_reconstruction.rs#L317-L345

The shared installation path prepares media in memory but intentionally leaves the recorded rollout unchanged. That means an existing 2.87 GB rollout remains large and repeats the same repair work on every reconstruction:

https://github.com/openai/codex/blob/0fb559f0f6e231a88ac02ea002d3ecd248e2b515/codex-rs/core/src/session/mod.rs#L1248-L1333

https://github.com/openai/codex/blob/0fb559f0f6e231a88ac02ea002d3ecd248e2b515/codex-rs/core/src/session/mod.rs#L1345-L1370

The zero-cost media retention that feeds this persistence path is documented in #33493. Current upstream counts InputImage and InputAudio as zero retained-message tokens and preserves them in partial messages:

https://github.com/openai/codex/blob/0fb559f0f6e231a88ac02ea002d3ecd248e2b515/codex-rs/core/src/compact_remote_v2.rs#L482-L560

I posted the detailed current-source trace and live-context implementation spec here:

https://github.com/openai/codex/issues/33493#issuecomment-5015810639

Proposed implementation / persistence spec

The smallest coherent stage should keep live and persisted replacement semantics identical:

  1. Before build_v2_compacted_history applies its retained-message budget, remove every retained raw InputImage and InputAudio payload while preserving a reopenable reference when one exists.
  • For canonical local-image triplets, retain the existing <image ... path="..."> wrapper and replace only the raw image item with bounded text instructing the model to use view_image if needed.
  • For pathless media, retain an explicit unavailable marker until a durable media-resource contract exists.
  1. Charge the reference/marker text against the existing retained-message token budget.
  2. Install that media-free history as the active post-compaction context.
  3. Let replace_compacted_history persist the same installed items unchanged. No second rollout-only representation is needed for the first fix.
  4. Keep the original user/response rollout records untouched so transcript rendering and audit history still retain the original event once. The change only prevents raw media from being recopied into every later checkpoint.
  5. For already-poisoned threads, sanitize media at or before the most recent compaction boundary in the cloned history used to construct the next compaction request. Preserve any adjacent source-path wrappers. Media added after that boundary remains visible to the current compactor; after the compactor summarizes it, the newly installed checkpoint becomes media-free but reference-preserving.
  6. Record omitted image/audio counts and bytes in compaction analytics so the size reduction and recovery path are observable.

A content-addressed media store remains useful for pathless pasted/data-URL images and transcript hydration, but it is not required to stop this reproduction because every observed image already has a source path. A later store should be owned by the image's execution environment and expose a stable reference that view_image can resolve; it must not turn an app-server host path into an assumed remote-executor path.

Historic rollout migration / vacuum spec

Existing rollouts need recovery before the next compaction:

  1. At the shared resume/fork reconstruction boundary, sanitize only the base replacement history selected from the latest surviving checkpoint. Leave the newer suffix untouched because it has not yet been summarized.
  2. Make the transformation deterministic and idempotent, preserving source-path wrappers while removing raw media.
  3. On resume, append one versioned repaired checkpoint with the same context-window number/IDs before accepting the first user/model turn.
  4. On fork, persist sanitized inherited context only in the child. Never rewrite or vacuum the parent as a side effect of forking.
  5. A migration/version marker must make later resumes no-ops rather than append equivalent repaired checkpoints.
  6. Keep a defensive pre-compaction repair for already-running sessions that did not pass through cold reconstruction.

The active-context repair and physical file reclamation should remain separate:

  1. Do not rewrite a multi-gigabyte JSONL synchronously on the resume path.
  2. After successful reconstruction and repaired-checkpoint persistence, schedule a vacuum under an exclusive rollout-writer lock.
  3. Stream to a same-directory temporary file. Strip raw media only from superseded compacted.replacement_history snapshots; preserve original response/image events and the latest repaired checkpoint.
  4. Preserve all unrelated fields, including opaque encrypted content, byte-for-byte.
  5. Parse/validate every output record, fsync the temporary file and directory, retain a recoverable backup, and atomically replace the original. Failure before replacement must leave the original selected and usable.
  6. Record bytes before/after, records rewritten, media bytes removed, duration, and failure status.
  7. Complete the reverse/lazy checkpoint reader so future resumes can stop at the latest repaired checkpoint without eagerly deserializing superseded multi-gigabyte snapshots. Until that lands, the vacuum is required to recover current resume latency as well as disk space.

Acceptance criteria

  • After successful compaction, CompactedItem.replacement_history contains no inline image/audio payloads.
  • Two consecutive compactions over the same image-heavy transcript do not copy prior-window data URLs into the second checkpoint.
  • Compacted-record byte size is bounded by retained text, bounded references/markers, canonical context, and the compaction item—not historical image bytes.
  • Resume, fork, rollback, and rollout-trace reconstruction produce the same sanitized history that was active immediately after compaction.
  • Original image-bearing response records remain available once for transcript/audit rendering.
  • Retained local-image references remain usable by a later view_image call in both local and remote-executor configurations.
  • Pathless or unavailable media degrades explicitly without embedding the original bytes.
  • Cold resume repairs a legacy image-heavy checkpoint before the first model request and does not remove unsummarized suffix images.
  • A second resume does not append a second repaired checkpoint.
  • Fork repair affects only the child rollout and does not modify the parent.
  • A regression test performs at least two compactions with large synthetic data URLs and asserts that checkpoint size does not grow with the discarded image payload size.
  • A recovery test starts from an existing image-bearing compacted replacement history and verifies that the next compaction request succeeds and installs a clean checkpoint.
  • A vacuum test verifies substantial byte reclamation while preserving reconstructed context, original transcript image events, and encrypted fields.
  • An interrupted/failed vacuum leaves the original rollout valid and selected.
  • Encrypted compaction/reasoning content remains opaque and byte-identical; the sanitizer only touches typed InputImage / InputAudio content items.

This should stop the 95%-of-rollout amplification demonstrated above while also fixing the post-compaction context death spiral, without changing the append-only rollout model or introducing replay divergence.

shlee1615 · 1 month ago

Additional Windows Desktop evidence (2026-07-26)

I reproduced the same on-disk amplification pattern on Windows with image-heavy / Computer Use sessions.

Environment

  • Product: Codex Desktop on Windows x64
  • App package version: OpenAI.Codex 26.721.4979.0
  • Session location: %USERPROFILE%\.codex\sessions

Aggregate impact

  • Entire Codex home: 97.234 GB
  • sessions directory: 93.97 GB across 1,794 JSONL files
  • 197 session files larger than 100 MB, totaling 91.55 GB
  • During the July 18–24 growth burst, 161 large session files accounted for 83.53 GB
  • July session growth alone was about 91.64 GB

Detailed sample

One representative rollout was 1.234 GB. A streaming structural scan (without printing or uploading any session content) found:

  • 2,331 inline image occurrences
  • Only 22 unique images by SHA-256
  • 2,309 duplicate occurrences
  • Inline Base64 image strings occupied 1.120 GB / 90.7% of the file
  • 2,309 image values were under payload.replacement_history[].content[].image_url
  • Only 20 image values were direct payload.content[].image_url values
  • Record mix for image-bearing lines was dominated by type=compacted

Representative largest-file samples from July 18–24 showed inline-image shares between 74.4% and 90.7% of each JSONL file. The repeated payloads were predominantly PNG data URLs, consistent with Computer Use screenshots being copied forward through compaction.

Observed behavior

A small number of real screenshots becomes thousands of Base64 occurrences as new compacted records persist replacement_history. This creates rapid write amplification across many sessions, not merely one unusually long transcript.

Expected behavior / requested fix

  • Externalize image blobs and persist content-addressed references.
  • Deduplicate identical screenshots across turns and compacted checkpoints.
  • Do not copy full historical image bytes into replacement_history.
  • Add per-record and per-session media-byte limits plus a repair/cleanup path for existing oversized rollouts.
  • Warn users when session storage or a single rollout exceeds a safe threshold.

Privacy note

No raw rollout, screenshots, prompts, local project paths, credentials, or logs are attached because session JSONL files contain private content. The measurements above were generated with streaming scans that emitted only counts, byte totals, MIME categories, and hashes.

deford827-droid · 29 days ago

Independent reproduction on Windows x64 with OpenAI.Codex 26.721.11231.0.

Current sanitized measurements:

  • sessions directory: 114.752 GB
  • rollout files over 500 MB: 73
  • rollout files over 1 GB: 29
  • rollout files over 2 GB: 17
  • largest rollout: 4.108 GB
  • approximately 3.94 GB of that rollout is occupied by records containing inline data:image payloads
  • the largest repeated compacted records are approximately 32.6 MB each

This confirms that the issue remains reproducible on a newer Windows Desktop build and can cause substantial disk-space consumption.

I am not attaching the raw rollout because it contains private prompts, screenshots, and local metadata.

Kanturna · 28 days ago

I can confirm the same failure mode on a current Windows Codex Desktop build. I am also the author of the Reddit report linked below.

Environment

  • OpenAI Codex Desktop: 26.721.11231.0
  • Bundled CLI: codex-cli 0.146.0-alpha.3.1
  • Windows x64
  • Long-running image/Computer Use tasks with repeated compaction and full-history subagent forks

Observed structural impact

  • My worst affected rollouts exceeded 2 GB.
  • Approximately 90–98% of their size consisted of inline image data.
  • Repeated input_image payloads were retained inside compacted.payload.replacement_history.
  • Matching images were multiplied further across full-history forks.
  • The Codex app-server reached nearly 9 GB of private memory.

I also observed a strong before/after runtime correlation:

  • With League of Legends closed, affected Codex work averaged roughly 10 Mbps outbound and peaked near 38 Mbps.
  • Gateway latency remained around 1 ms, while public latency spiked as high as approximately 195 ms.
  • After a schema-aware offline repair that removed only duplicated images from compacted replacement history—while preserving the original images, text, task IDs, and verified backups—the affected tasks became 61–93% smaller.
  • During subsequent real Codex work, outbound traffic averaged approximately 0.1–0.2 Mbps, while public latency remained around 9 ms.

I want to keep the attribution precise: image retention and duplication inside compacted history were structurally confirmed. The before/after network behavior strongly supports a connection, but adapter-level measurements alone do not prove that every transferred byte or latency spike was caused by Codex.

I documented the symptoms, diagnostic prompt, safety constraints, and recovery approach here:

https://www.reddit.com/r/codex/comments/1vaw26v/codex_desktop_duplicated_inline_screenshots/

I am not uploading raw rollout files because they contain private screenshots, prompts, project paths, and other local data. I can provide sanitized structural counts, shortened hashes, and before/after measurements if useful.

Feedback ID: 019fb31e-ced3-78c1-9b41-38dc4f8c430c

J0EY0 · 27 days ago

Additional macOS Desktop evidence: one continuously used thread reached 6.11 GiB

I observed the same append-only compaction amplification in a single long-running Codex Desktop thread containing many pasted screenshots.

The measurements below were produced with a streaming structural scan that recorded only JSONL record types, counts, and serialized byte lengths. No prompts, screenshots, local paths, tool outputs, or raw JSONL records were printed or uploaded.

Environment
  • Affected session metadata:
  • cli_version: 0.142.0
  • originator: Codex Desktop
  • Current installation at inspection time:
  • Codex Desktop: 26.727.40816 (build 6067)
  • Bundled CLI: codex-cli 0.146.0-alpha.9.2
  • macOS: 15.7.4 (24G517)
  • Architecture: Apple Silicon (arm64)
  • Usage pattern: one continuously reused main Desktop thread over several weeks, with many pasted screenshots
Sanitized structural measurements

| Metric | Observed value |
|---|---:|
| Rollout JSONL size | 6,559,046,373 bytes (6.11 GiB) |
| Total JSONL records | 60,233 |
| compacted records | 327 |
| Bytes occupied by compacted records | 6,308,733,046 bytes (96.18%) |
| Records larger than 10 MiB | 314 |
| Largest compacted record | 24,825,686 bytes (23.68 MiB) |
| Latest replacement_history | 494 items / 24,812,465 bytes |
| Retained input_image parts | 135 |
| Image URL format | 135 × data:image/png;base64 |
| Inline image URL bytes in latest snapshot | 24,531,498 bytes (23.39 MiB) |
| Input text bytes in latest snapshot | 132,259 bytes (about 129 KiB) |
| Byte-identical duplicate large records | 0 |

Observed behavior
  1. Approximately 96% of the rollout file consists of appended compacted records.
  2. The latest compaction snapshot is almost entirely inline PNG image data.
  3. Historical screenshots remain embedded as Base64 data URLs inside later replacement_history snapshots.
  4. Each compaction appends another evolving full snapshot instead of referencing previously stored media.
  5. The records are not byte-identical duplicates, but they retain substantial overlapping historical image data.
  6. A single long turn can trigger multiple compactions and append more than 100 MiB.
  7. This occurred in the main thread and did not require subagent fan-out.

This appears to be append-only snapshot amplification rather than recursively nested replacement_history objects.

Expected behavior
  • Store image/media payloads once and reference them by attachment ID or content hash.
  • Bound the number or total bytes of images retained after compaction.
  • Avoid indefinitely retaining superseded full image-heavy compaction snapshots.
  • Provide garbage collection, size limits, or a warning before a thread reaches multi-gigabyte size.
Privacy note

I am intentionally not attaching the rollout file because it contains private prompts, screenshots, local paths, and tool output. I can provide additional redacted structural measurements if maintainers need a specific counter or distribution.

waml · 27 days ago

this is very worrying almost 52gigs filled because of this bug it should be fixed ASAP

Kanturna · 27 days ago

Local source-level prevention proof of concept

I followed up on my previous report by implementing and testing a local source-level proof of concept against the exact bundled CLI version I was running: codex-cli 0.146.0-alpha.3.1.

This is not an upstream fix, and I am not distributing a custom executable.

Implementation boundary

The important part of the implementation is that this is not a persistence-only filter:

  1. The compaction request can still process the current full media.
  2. After the compaction response has processed it, inline media is removed from the resulting replacement history.
  3. That sanitized history then becomes both:
  • the active post-compaction context
  • the persisted CompactedItem.replacement_history

This keeps live and resumed semantics aligned.

The sanitizer:

  • removes only typed inline image/audio payloads
  • preserves ordinary text and non-inline references
  • explicitly leaves EncryptedContent untouched
  • inserts a small text placeholder for removed historical media
  • covers both remote compaction implementations
  • adds a defensive check at replace_compacted_history
  • sanitizes selected legacy replacement history during resume
  • handles rollback-aware fork inheritance while preserving the unsummarized live suffix
  • does not modify the parent rollout as a side effect of forking

Original top-level image events remain in the append-only transcript. The change only prevents them from being copied into every later compacted checkpoint.

Validation completed

The local patch passed:

  • compact-media unit tests
  • feature-registry tests
  • rollback-specific reconstruction tests
  • the relevant broader compaction test suite
  • an image-capable v2 compaction integration test
  • nested/subagent compaction coverage
  • fork-history coverage
  • release-mode check and build
Live result

After restarting Codex with the patched binary and feature enabled, I produced a fresh Computer Use screenshot and then ran /compact.

The resulting rollout showed:

  • the new compacted checkpoint occurred after the screenshot
  • 104 replacement-history items
  • 0 inline image/audio payloads in the latest replacement history
  • 0 inline media items in the active suffix after compaction
  • 0 JSON parse errors

The original historical media records still exist earlier in the append-only journal, but they are no longer part of the active replacement history and were not copied into the new checkpoint.

One precision note: that specific live screenshot arrived as a custom_tool_call_output, so the live check proves the desired post-compaction context result. The direct retained input_image sanitization path was exercised separately by the image-capable integration test.

Known limitations

This proof of concept does not yet provide:

  • automatic vacuuming of old multi-gigabyte rollout files
  • a content-addressed external media store
  • byte-based retention budgets or UI warnings
  • automatic physical reclamation of original top-level image records
  • guaranteed compatibility with later Codex versions

For pathless media, the original pixels are intentionally unavailable to the model after compaction.

Does this implementation boundary align with the intended upstream solution? If useful, I can provide the focused source patch and test details, or open a PR if explicitly invited under the repository’s contribution policy.

akimovdmitry · 15 days ago

Additional Windows data point — the same issue is still present in a much newer build and has reached an extreme total size.

Environment:

  • Codex Desktop for Windows: 26.803.10989.0
  • Windows 11 x64
  • Session storage: %USERPROFILE%\.codex\sessions

Measured impact:

  • sessions directory: 322.772 GiB
  • rollout files: 642
  • largest rollout: 46.586 GiB
  • files created during the last 48 hours: 132
  • combined size of those new files: 15.261 GiB

Exact streaming analysis of one newly created 393.13 MiB rollout:

  • compacted records: 62
  • bytes in compacted records: 345.98 MiB (~88% of the file)
  • largest compacted record: 13.23 MiB
  • data:image references: 285 across 74 JSONL records
  • response_item/message: 27.86 MiB
  • custom tool outputs: 4.58 MiB

So this is not normal text/tool logging. Repeated compacted checkpoints carrying inline image data dominate the persistent writes. The machine becomes noticeably laggy during Codex use, and storage keeps growing by roughly 7–8 GiB/day in the recent sample.

I am not attaching the rollout files because they contain private prompts, tool output, local paths, and images. The measurements above were collected with a streaming parser.

This looks like the same root cause described here, but the 322.8 GiB aggregate footprint shows that the missing retention/deduplication limit can affect the entire Codex profile, not only one thread. A built-in repair/cleanup action and hard per-thread/profile limits would be important.

shleder · 14 days ago

I’m testing Codex Rescue 0.1.0a3 on real oversized/compacted rollout cases. Your repeated inline-image payloads inside compacted.payload.replacement_history are especially useful because Rescue can detect oversized persisted records without rewriting the source rollout.

It does not strip images or mutate compaction history automatically; the goal is to see whether it diagnoses the boundary safely and produces a bounded handoff with REVIEW_REQUIRED when needed.

If you still have one affected rollout, would you be willing to run:

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

Sanitized salvage --fork / verify output would also help. Please don’t post raw JSONL, images, prompts, encrypted content, DBs, credentials, or private paths.

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