Repository navigation
Desktop compaction repeatedly embeds full image base64 in compacted checkpoints #23257
Description
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 compactedrecords:128- Total bytes in
compactedrecords:770.9 MB - Lines containing
data:image:315 - Total bytes in lines containing
data:image:822.5 MB - Total
data:imagereferences:1,787 - Latest
compactedrecord size:7.66 MB - Latest
compactedimage refs:15 - Latest
compacted.payload.replacement_historylength:185 - Latest
compactedvisible string chars before image removal: about7,954,167 - Latest
compactedcontained15image 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:
compactedrecords:1data:imagerefs:0- Latest
compactedsize: about316 KB - Latest
replacement_historylength remained185 - 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 compactedrecords: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%/139ktokens 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
- Use Codex Desktop in a long-running thread.
- 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. - Let Codex automatically compact the context several times.
- Inspect the saved rollout JSONL.
- Observe many
compactedrecords and a latestcompacted.payload.replacement_historythat still contains full image payloads. - 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_contentopaque 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
- Desktop threads can be poisoned by inline base64 tool images, leading to
{"detail":"Bad Request"}on resume and likely inflated token usage #18629 - macOS Desktop: image-heavy thread takes minutes to open because rollout JSONL embeds generated image payloads inline #22603
- Context remaining/token estimate overcounts MCP image data URLs (base64) #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.
Activity
github-actions commentedon May 18, 2026
Potential duplicates detected. Please review them and close your issue if it is a duplicate.
RangeError: Invalid string lengthwhen loading sessions whose rollout JSONL exceeds V8's max string length #22004Powered by Codex Action
banteg commentedon Jun 22, 2026
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:
Rollout-level measurements
One local rollout JSONL from 2026-04-24 grew to about
11.36 GiBby the last compaction point.104,082compactedrecords:287compactedrecords:11,465,609,653bytes, about10.68 GiBcompactedrecord:54,639,204bytes, about52.1 MiB11.36 GiB10.68 GiBSo in this sample, compacted checkpoints alone account for nearly all of the rollout file size.
The records are not opaque
The large
compactedrecords are normal structured JSON:type: "compacted"payload.messagepayload.replacement_historyreplacement_historyis an array of response itemsThe largest compacted record contained exactly one
replacement_historykey in the whole JSON record, so this was not recursive self-embedding of priorreplacement_historyblobs.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:
286 / 287input_imageparts across compacted snapshots:9,17311,441,321,095bytes, about10.66 GiBimage_urlstrings alone:11,440,834,926bytes, about10.66 GiBinput_imageJSON object:4353,765,897bytes, about51.3 MiB53,929, but only about13.9 MiBtotalcompactionitems inside histories: about6.0 MiBtotalThat means about
51 MiBof unique image payloads were repeatedly copied into compacted checkpoints until they contributed about10.66 GiBon disk, roughly a~213xamplification in this rollout.The largest compacted record showed the same pattern locally:
54,639,204bytesreplacement_historyitems:155input_imageparts in that record:4454,536,192bytesThe first inspected image part had keys
type,image_url, anddetail; theimage_urlfield alone was about1.29 MB.Interpretation
This looks like append-only snapshot amplification:
replacement_historysnapshot.image_urlpayloads remain in that replacement history.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 commentedon Jun 30, 2026
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:
Structural evidence from the affected rollout JSONL before mitigation:
payload.replacement_historyitems: 1678input_imageparts: 145image_urlchars in latest replacement history: 41,113,066Observed behavior:
Manual mitigation that restored usability for this thread:
input_imagecontent items by replacing them with shortinput_textplaceholders.input_imagecount: 0; JSON parse errors: 0.Expected behavior / possible product fixes:
wgu9 commentedon Jun 30, 2026
Additional current-main source trace that seems to match the reported amplification:
CompactedItemis schema-backed asreplacement_history: Option<Vec<ResponseItem>>incodex-rs/protocol/src/protocol.rs.codex-rs/core/src/compact_remote.rsbuildsCompactedItem { replacement_history: Some(new_history.clone()), ... }, andcodex-rs/core/src/compact_remote_v2.rsdoes the same for v2.CodexThread::replace_compacted_historyincodex-rs/core/src/session/mod.rs: after item-id assignment it overwrites the compacted item withreplacement_history: Some(items.clone())and then appendsRolloutItem::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
ResponseItemstill contains inlineinput_image.image_urldata, every later compaction writes another full copy into a new append-only JSONL line. That matches the measurements above: the bloat is not recursivereplacement_historynesting; it is repeated full snapshots of retained inline image payloads.Suggested fix boundary:
CompactedItem.replacement_history, sanitize or externalize large image payloads inside the retainedResponseItems. Options: content-addressed image blobs plus lightweight refs, explicit historical-image placeholders, or a byte/count/age retention budget for image parts.compact_remoteandcompact_remote_v2, ideally at or just beforereplace_compacted_history, so local/remote/v2 behavior cannot diverge.Regression shape:
input_imagedata URLs.ignatremizov commentedon Jul 19, 2026
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:
compactedrecordscompactedrecordscompactedrecordsThe 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_historyobjects. 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
CompactedItemwith a clone of the newly installed history:codex/codex-rs/core/src/compact_remote_v2.rs
Lines 285 to 319 in 0fb559f
replace_compacted_historythen deliberately overwritesreplacement_historywith another clone of the exact post-ID-assignment live history and appends it as aRolloutItem::Compactedrecord:codex/codex-rs/core/src/session/mod.rs
Lines 3021 to 3051 in 0fb559f
The persisted schema stores that snapshot directly as
Option<Vec<ResponseItem>>:codex/codex-rs/protocol/src/protocol.rs
Lines 3232 to 3248 in 0fb559f
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:
codex/codex-rs/core/src/session/rollout_reconstruction.rs
Lines 113 to 185 in 0fb559f
codex/codex-rs/core/src/session/rollout_reconstruction.rs
Lines 317 to 345 in 0fb559f
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:
codex/codex-rs/core/src/session/mod.rs
Lines 1248 to 1333 in 0fb559f
codex/codex-rs/core/src/session/mod.rs
Lines 1345 to 1370 in 0fb559f
The zero-cost media retention that feeds this persistence path is documented in #33493. Current upstream counts
InputImageandInputAudioas zero retained-message tokens and preserves them in partial messages:codex/codex-rs/core/src/compact_remote_v2.rs
Lines 482 to 560 in 0fb559f
I posted the detailed current-source trace and live-context implementation spec here:
#33493 (comment)
Proposed implementation / persistence spec
The smallest coherent stage should keep live and persisted replacement semantics identical:
build_v2_compacted_historyapplies its retained-message budget, remove every retained rawInputImageandInputAudiopayload while preserving a reopenable reference when one exists.<image ... path="...">wrapper and replace only the raw image item with bounded text instructing the model to useview_imageif needed.replace_compacted_historypersist the same installed items unchanged. No second rollout-only representation is needed for the first fix.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_imagecan 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:
The active-context repair and physical file reclamation should remain separate:
compacted.replacement_historysnapshots; preserve original response/image events and the latest repaired checkpoint.Acceptance criteria
CompactedItem.replacement_historycontains no inline image/audio payloads.view_imagecall in both local and remote-executor configurations.InputImage/InputAudiocontent 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 commentedon Jul 26, 2026
Additional Windows Desktop evidence (2026-07-26)
I reproduced the same on-disk amplification pattern on Windows with image-heavy / Computer Use sessions.
Environment
OpenAI.Codex 26.721.4979.0%USERPROFILE%\.codex\sessionsAggregate impact
sessionsdirectory: 93.97 GB across 1,794 JSONL filesDetailed sample
One representative rollout was 1.234 GB. A streaming structural scan (without printing or uploading any session content) found:
payload.replacement_history[].content[].image_urlpayload.content[].image_urlvaluestype=compactedRepresentative 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
compactedrecords persistreplacement_history. This creates rapid write amplification across many sessions, not merely one unusually long transcript.Expected behavior / requested fix
replacement_history.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.
10 remaining items
Kanturna commentedon Jul 30, 2026
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
26.721.11231.0codex-cli 0.146.0-alpha.3.1Observed structural impact
2 GB.90–98%of their size consisted of inline image data.input_imagepayloads were retained insidecompacted.payload.replacement_history.9 GBof private memory.I also observed a strong before/after runtime correlation:
10 Mbpsoutbound and peaked near38 Mbps.1 ms, while public latency spiked as high as approximately195 ms.61–93%smaller.0.1–0.2 Mbps, while public latency remained around9 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
docs(compaction): archive image rollout implementation spec
J0EY0 commentedon Jul 31, 2026
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
cli_version: 0.142.0originator: Codex Desktop26.727.40816(build6067)codex-cli 0.146.0-alpha.9.215.7.4 (24G517)arm64)Sanitized structural measurements
compactedrecordscompactedrecordscompactedrecordreplacement_historyinput_imagepartsdata:image/png;base64Observed behavior
compactedrecords.replacement_historysnapshots.This appears to be append-only snapshot amplification rather than recursively nested
replacement_historyobjects.Expected behavior
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 commentedon Jul 31, 2026
this is very worrying almost 52gigs filled because of this bug it should be fixed ASAP
Kanturna commentedon Jul 31, 2026
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:
CompactedItem.replacement_historyThis keeps live and resumed semantics aligned.
The sanitizer:
EncryptedContentuntouchedreplace_compacted_historyOriginal 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:
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:
104replacement-history items0inline image/audio payloads in the latest replacement history0inline media items in the active suffix after compaction0JSON parse errorsThe 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 retainedinput_imagesanitization path was exercised separately by the image-capable integration test.Known limitations
This proof of concept does not yet provide:
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 commentedon Aug 13, 2026
Additional Windows data point — the same issue is still present in a much newer build and has reached an extreme total size.
Environment:
Measured impact:
Exact streaming analysis of one newly created 393.13 MiB rollout:
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.