Local compaction v2 retains unbounded input_image payloads, causing repeated auto-compaction
What version of Codex is running?
- Codex Desktop:
26.707.72221 - Embedded CLI:
codex-cli 0.144.2 - Platform: macOS 26.3 (Build 25D125), Apple Silicon
- Model:
gpt-5.6-sol
What issue are you seeing?
A long-running, image-heavy Codex Desktop thread enters a repeated auto-compaction cycle. After a compaction completes, the resulting history remains only slightly below the auto-compaction threshold. Even a short new question pushes it over the threshold again, so almost every new turn starts with another compaction.
The compacted replacement_history keeps historical input_image items, including their full inline base64/data-URL payloads. Repeated compactions keep copying the same images into new compacted records.
This produces two related effects:
- The post-compaction prompt remains close to the context threshold, causing immediate re-compaction on subsequent turns.
- The rollout JSONL grows rapidly because the same inline image payloads are persisted repeatedly.
Local evidence
From one affected thread:
| Metric | Observed value |
| --- | ---: |
| Rollout JSONL size | approximately 4.16 GB |
| Compaction records | 224 |
| Images retained by the latest compacted replacement history | 92 |
| Retained inline image/base64 characters | approximately 26.47 million |
| Retained text characters | approximately 188,000 |
| Effective context window reported for the session | 258,400 tokens |
| Default auto-compaction threshold | approximately 244,800 tokens |
| Prompt size immediately after compaction | approximately 236,000-237,000 tokens |
The post-compaction history therefore has only about 8,000 tokens of headroom. A normal follow-up turn is sufficient to trigger compaction again.
No full rollout is attached because it contains private prompts, paths, tool output, and images. I can provide additional redacted structural statistics if needed.
Steps to reproduce
- Use a long-running Codex Desktop thread containing multiple pasted screenshots or image inputs.
- Continue until local/remote-v2 compaction occurs.
- Inspect the newest
type: "compacted"rollout record. - Confirm that
payload.replacement_historyretains user messages containinginput_imagedata URLs. - Send a short follow-up question.
- Observe another automatic compaction even though the previous compaction just completed.
- Repeat several turns and observe the rollout file growing as image payloads are copied into additional compacted records.
Expected behavior
After compaction:
- Retained multimodal history should be bounded by a realistic image/token/byte budget.
- There should be enough hysteresis below the auto-compaction threshold for ordinary follow-up turns.
- Old inline image payloads should not be duplicated indefinitely in every compacted rollout record.
Actual behavior
The current retained-message text budget treats images as zero text tokens and preserves them:
ContentItem::InputImage { .. } => 0,
and later:
ContentItem::InputImage { .. } => truncated_content.push(content_item),
The message-level .max(1) floor limits the number of image-only messages only very weakly. It does not account for image cost inside mixed text/image messages, image count, encoded byte size, or vision-token cost.
Current source:
codex-rs/core/src/compact_remote_v2.rsmessage_text_token_counttruncate_message_text_to_token_budgettruncate_retained_messages_for_remote_compaction
Possible fix direction
For active compacted history:
- Give retained images a conservative estimated token cost, using the existing image-size/detail estimator where possible.
- Add explicit caps for retained image count and/or encoded bytes.
- Keep the newest images first and replace older images with a lightweight placeholder once the image budget is exhausted.
- Add a regression test with many mixed text/image messages verifying that compacted history is bounded and a short next turn does not immediately trigger compaction again.
For rollout persistence, consider storing images as content-addressed blobs or references rather than repeating full base64 strings. That storage concern may be better handled separately under the existing image-heavy rollout issue.
Related issues
- #24388: same underlying retained-
input_imageproblem, closed after the compaction architecture changed; this report reproduces the behavior on the newer local compaction path. - #24550: large inline images in compacted
replacement_historycause Responses WebSocket fallback. - #24676: image-heavy rollout JSONL causes Codex Desktop hangs and difficult session recovery.
- #9601: images can poison context estimation after history mutation.
I am willing to help with additional redacted diagnostics, test-case design, or an implementation if maintainers agree on the approach and invite a PR under the repository contribution policy.
24 Comments
Potential duplicates detected. Please review them and close your issue if it is a duplicate.
Powered by Codex Action
Additional current reproduction and source trace (2026-07-19).
Reproduction
I inspected another long-lived image-heavy thread after a local compaction-v2 checkpoint. Sanitized structural measurements from the latest
compacted.payload.replacement_history:| Metric | Observed value |
| --- | ---: |
| Rollout JSONL size | 2,873,784,216 bytes |
| Compaction records | 54 |
| Replacement-history items | 211 |
| User messages | 209 |
| Retained
input_imageparts | 142 || Retained image detail | 142 ×
high|| Retained inline image/data-URL characters | 111,563,732 |
| Retained text characters | 56,733 |
| Effective model context window | 353,400 tokens (
gpt-5.6-sol) |The token sequence makes the impact visible:
total_tokens=294,141,input_tokens=0, which rendered as 17% context remaining.input_tokens=342,406,output_tokens=230,total_tokens=342,636, which rendered as 3% remaining.The apparent drop was not caused by the file-read output displayed immediately before the second token event. That usage belongs to the request that generated the read call, so the result could not yet have been part of its input. The first server-provided usage corrected the lower-bound local estimate for the already-installed image-heavy prompt.
No raw rollout is attached because it contains private prompts, paths, tool output, and images. The counts above were computed structurally without printing content.
All 142 retained images in this reproduction were already surrounded by the canonical local-image text wrapper: an opening
<image ... path="...">item immediately before theInputImageand a closing</image>immediately after it. The compactor can therefore remove the inline bytes without losing the model-callable source path.Current upstream source analysis
Current upstream
mainnow uses a 64k retained-message budget, but the budget remains text-only:https://github.com/openai/codex/blob/0fb559f0f6e231a88ac02ea002d3ecd248e2b515/codex-rs/core/src/compact_remote_v2.rs#L52-L54
build_v2_compacted_historyselects retained user/developer/system messages from the compaction prompt and passes them through the retained-message truncator:https://github.com/openai/codex/blob/0fb559f0f6e231a88ac02ea002d3ecd248e2b515/codex-rs/core/src/compact_remote_v2.rs#L443-L460
The truncator charges only text.
InputImageandInputAudiocontribute zero, and the partial-message path always preserves them:https://github.com/openai/codex/blob/0fb559f0f6e231a88ac02ea002d3ecd248e2b515/codex-rs/core/src/compact_remote_v2.rs#L482-L560
The current tests explicitly lock in this behavior by asserting that images survive truncation and that an image-only message costs only the
.max(1)message floor:https://github.com/openai/codex/blob/0fb559f0f6e231a88ac02ea002d3ecd248e2b515/codex-rs/core/src/compact_remote_v2.rs#L729-L819
Local-image prompt construction already emits the source path in an adjacent model-visible wrapper and keeps the raw image as a separate content item:
https://github.com/openai/codex/blob/0fb559f0f6e231a88ac02ea002d3ecd248e2b515/codex-rs/protocol/src/models.rs#L1392-L1400
https://github.com/openai/codex/blob/0fb559f0f6e231a88ac02ea002d3ecd248e2b515/codex-rs/protocol/src/models.rs#L1568-L1588
view_imagealready accepts a model-generated path plus an optional environment ID and resolves it through the selected environment filesystem:https://github.com/openai/codex/blob/0fb559f0f6e231a88ac02ea002d3ecd248e2b515/codex-rs/core/src/tools/handlers/view_image.rs#L53-L57
https://github.com/openai/codex/blob/0fb559f0f6e231a88ac02ea002d3ecd248e2b515/codex-rs/core/src/tools/handlers/view_image.rs#L118-L168
There is already a content-aware image estimator elsewhere in core. It replaces inline base64 payload size with an estimated model-visible image cost (approximately 1,844 tokens for ordinary resized images, with separate original-detail patch accounting):
https://github.com/openai/codex/blob/0fb559f0f6e231a88ac02ea002d3ecd248e2b515/codex-rs/core/src/context_manager/history.rs#L521-L579
https://github.com/openai/codex/blob/0fb559f0f6e231a88ac02ea002d3ecd248e2b515/codex-rs/core/src/context_manager/history.rs#L652-L679
That estimator is used after history replacement for the provisional token-count event, but not for retained-message selection.
recompute_token_usagealso estimates only base instructions plus installed history and marks the result asinput_tokens=0, explaining why the first real server count can jump materially:https://github.com/openai/codex/blob/0fb559f0f6e231a88ac02ea002d3ecd248e2b515/codex-rs/core/src/session/mod.rs#L3668-L3704
Historic rollouts currently receive no equivalent cleanup. Reconstruction selects the latest surviving
replacement_historyas its base and installs it exactly before replaying the suffix: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
Both resume and fork use the shared reconstruction path. It currently prepares legacy media in memory while explicitly leaving the recorded rollout unchanged, so image-heavy checkpoints remain active and are processed again 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
Proposed implementation / fix spec
I suggest landing the smallest coherent safety fix first: raw media bytes should not cross a successful compaction boundary, but reopenable source references should. The compaction output is already the semantic handoff for the prior window.
InputImageandInputAudioparts before retained-message budgeting.<image ... path="...">,InputImage,</image>sequence, preserve the existing path wrapper and replace onlyInputImagewith bounded text such asImage bytes removed after compaction; use view_image with the path above if needed.build_v2_compacted_historybeforetruncate_retained_messages_for_remote_compaction.reconstruct_history_from_rolloutalready distinguishes the latest basereplacement_historyfrom the newer rollout suffix.run_remote_compact_v2_attemptfor already-running sessions upgraded in place or histories that bypass cold reconstruction.for_prompt()constructs the compaction request:https://github.com/openai/codex/blob/0fb559f0f6e231a88ac02ea002d3ecd248e2b515/codex-rs/core/src/compact_remote_v2_attempt.rs#L41-L85
ResponseItem::Compaction/ContextCompactionboundary.CompactedItem.replacement_historysnapshots.omitted_image_count,omitted_audio_count, omitted inline-media bytes, repair-applied/version, vacuum bytes before/after, and vacuum failure status. Keepretained_image_countfor verifying that the installed replacement reaches zero under this policy.view_imagepaths remain strings at the tool boundary, but resolution should continue through the selected environment and its filesystem.environment_id.Acceptance criteria
replacement_historyafter compaction contains no rawInputImageorInputAudiopayloads.view_imagewith a retained path and successfully reload the image when that source remains available.environment_id; a host-only path is never treated as an executor path.InputAudioreceives the same treatment so the parallel zero-cost path does not become the next instance of this bug.This would address both the immediate context death spiral here and the repeated checkpoint amplification tracked in #23257 without requiring a new persisted-history representation.
i can confirm this on another affected Codex 0.145.0 session.
i used a read-only structural scan. i did not inspect or upload prompts, paths, images, tool output, project names, or thread IDs.
After one successful compaction, the installed replacement history still contained 32 unique PNG
input_imageitems. All haddetail: high. The inline payloads used about 18.8 million base64 characters. The existing estimator in Codex maps these images to about 59,000 model-visible image tokens.The first real server usage after that compaction reported 104,988 input tokens and 13,056 cached input tokens. This is consistent with a compacted checkpoint that still keeps a large image floor.
Current
mainat808d3c2702ce8eae007c457aa930e7c3b68dd5f6still gives images and audio zero cost during retained-message selection and then keeps them:https://github.com/openai/codex/blob/808d3c2702ce8eae007c457aa930e7c3b68dd5f6/codex-rs/core/src/compact_remote_v2.rs#L439-L556
The live history is then prepared with
for_prompt()before each model request:https://github.com/openai/codex/blob/808d3c2702ce8eae007c457aa930e7c3b68dd5f6/codex-rs/core/src/session/turn.rs#L292-L299
The same codebase estimates an ordinary resized image as about 1,844 tokens:
https://github.com/openai/codex/blob/808d3c2702ce8eae007c457aa930e7c3b68dd5f6/codex-rs/core/src/context_manager/history.rs#L493-L506
This does not prove an exact image-only token cost because usage events do not include a modality split. It does confirm a mismatch between retained-message selection and the model-visible estimator.
i agree with the invariant proposed above. Raw media from the compacted window should not enter the installed replacement history. A bounded reference or unavailable marker can remain, while the original media stays available to the transcript. i am not posting the rollout because it contains private data.
If maintainers want legacy repair and physical vacuum tracked separately, i can open a focused follow-up issue.
Adding a corroborating data point for this on macOS, because the impact went beyond disk and into a very large token bill.
Environment
What happened
One image-heavy thread (lots of generated images over a long session) grew past 1 GB. From there it matches the mechanism described in this issue exactly: the inline image data gets re-serialized on compaction/resume, so the session is re-written in full again and again instead of referencing prior image content.
On-disk evidence (
~/.codex/sessions)The disk (460 GB) filled to 0 bytes twice in one day, making the machine unusable (writes failing with ENOSPC system-wide). Multiple Codex renderer processes sat at 70 to 75% CPU while this happened.
Token / cost impact (this is the part I'd flag)
Because each resume re-feeds the entire ~1 GB session as context, usage tracking (ccusage) reported roughly 2.68 billion input tokens and ~73 billion cached input tokens for Codex/gpt-5 in a single day, estimated at about $35,700 for that one day!!! Non-Codex usage on the same machine that day was under $100.
I suspect this is #33735 (giant sessions from duplicated inline images) compounding with #22411 (app-server eager-loading all session files on every
thread/list): once each session is multiple GB, repeatedly re-reading them all turns the disk bloat into a massive background token burn as well. So the two issues together produce a much worse outcome than either alone.Suggested fixes (echoing and extending this thread)
replacement_historyrecords.Workaround
Fully quit the ChatGPT/Codex app (so no session files are held open), then delete the redundant oversized rollout copies from
~/.codex/sessionswhile preserving the normal small threads. That reclaimed ~50 GB in my case.Analysis (community)
Residual analysis for #33493 (compaction v2 retains unbounded
input_imagepayloads → repeated auto-compaction) under docs/contributing.md — invitation-only. No unsolicited PR.Root-cause hypothesis
The images are only the trigger. The reason the cycle is relentless rather than occasional is a missing fixed-point guarantee in the compaction loop: nothing ever checks whether the post-compaction history actually fits below the auto-compact threshold, so any retained payload (images counted at 0 tokens, an uncapped chained summary) keeps the prompt at ~97% of the limit and re-fires compaction on every subsequent turn — while each fire appends another full
replacement_historysnapshot to an append-only rollout with no GC. Verified at HEAD4c43465133:1. Images are counted as 0 tokens and budget-exempt — exactly as reported. In the v2 truncation pass,
message_text_token_countmapsContentItem::InputImageandInputAudioto0(codex-rs/core/src/compact_remote_v2.rs:505-525), andtruncate_retained_messages_for_remote_compaction(compact_remote_v2.rs:478-502) only ever truncates text viatruncate_message_text_to_token_budget— an image-only message sails through the 64kRETAINED_MESSAGE_TOKEN_BUDGET(:56) untouched. The local path has no image budget at all. Your 92 retained images / 26.47M base64 chars in the newest compacted record is this code path.2. No post-compaction fits-check → no fixed point. After compaction,
recompute_token_usageonly re-estimates (compact.rs:384); nothing compares the result againstauto_compact_token_limit()(=context_window * 9/10,protocol/src/openai_models.rs:459-470). Pre-turn compaction fires on every turn whentoken_limit_reached(session/turn.rs:846-872), and the mid-turn rollover path carries the dev comment "as long as compaction works well in getting us way below the token limit, we shouldn't worry about being in an infinite loop" (turn.rs:394). With ~8k headroom after compaction (your measurement), one normal follow-up turn crosses 90% again — the loop has no exit condition. There is also no re-fire guard: nothing records "just compacted, usage unchanged" (state/session.rshas no such marker).3. The retained summary is uncapped and chained. Each compaction builds replacement history from ≤20k tokens of recent user messages (
COMPACT_USER_MESSAGE_MAX_TOKENS,compact.rs:56) plusSUMMARY_PREFIX + summary_suffixwhere the suffix is the last assistant message of the compaction turn (compact.rs:347-353) — i.e. the prior summary re-summarized, appended with no size cap inbuild_compacted_history(compact.rs:671-684). Text alone can therefore hold the history near threshold across compactions even after the images are fixed.4. Every compaction appends a full snapshot; the chain is never pruned.
replace_compacted_historypersistsRolloutItem::Compactedcarrying the completereplacement_history, plus fullWorldStateandTurnContextitems (session/mod.rs:3168-3215), into an append-only writer. Reconstruction consumes only the newest surviving checkpoint (session/rollout_reconstruction.rs:156-186— the reverse scan takes the firstreplacement_historyit meets and replays only the suffix). Every older snapshot is provably dead weight, and there is no GC/retention pass anywhere inrollout/orcore/. Your 4.16 GB / 224 compaction records — and the closed #24191's 892 MB / 62 records — are this mechanic; image payloads just make each record ~100x larger.5. The overflow safety valve is itself unbounded. On
ContextWindowExceededthe local path removes ONE oldest history item, resetsretries = 0, and re-issues a full remote stream call (compact.rs:309-324) — an item-count-bounded retry loop with no constant cap; the remote path treats it as non-retryable (protocol/src/error.rs:358-396), which is the terminalcontext_length_exceededsibling of this issue.Ranked fix outline (all OSS, codex-rs):
input_imageat a real token estimate (or a conservative per-image constant) inmessage_text_token_count, and lettruncate_retained_messages_for_remote_compactiondrop/placeholder oldest images when over budget — closes the reported trigger inside the code the v2 fix introduced.recompute_token_usage, if usage is still ≥ the auto-compact limit, escalate deterministically (drop injected initial context → halve retained user-message budget → cap summary) and, failing that, emit an actionable terminal error instead of silently re-arming the same loop. This replaces theturn.rs:394assumption with a structural guarantee.build_compacted_history.Compacted, a best-effort pruner (temp-file + rename, modeled on the existing cold-compression worker inrollout/src/compression.rs) keeping SessionMeta + newest checkpoint + suffix bounds on-disk growth to O(window) instead of O(total tokens). Largest diff; also relieves the rollout-size half of this issue.MAX_TRIM_PASSESand bail with an actionable error before reaching 1 item.Fail-first tests I'd want before any merge: (1) a retained history with 26M chars of
input_imageand <1k tokens of text is truncated by the v2 budget pass (today: passes through intact); (2) after a compaction whose recomputed usage is still ≥ threshold, the next turn does NOT immediately re-fire an identical PreTurn compaction (today: it does); (3) K compactions leave exactly 1 liveCompactedsnapshot plus suffix in the rollout (today: K full snapshots). Happy to draft the test patch if a maintainer wants it.Related: #32888 is the stale-token-usage sibling on the same resume/trigger axis (resume seeds live usage from the last persisted
TokenCount,session/mod.rs:1340-1345); #33235 was auto-flagged as a possible duplicate. No unsolicited PR from me.Adding a sanitized reproduction from a newer macOS/App/CLI combination. This corroborates the existing diagnosis and quantifies the on-disk amplification from repeated inline images.
Environment
26.721.41059(build5848)cli_version 0.145.0-alpha.18codex-cli 0.146.026.5.2 (25F84), Apple Silicongpt-5.6-solSanitized structural measurements
The affected rollout was inspected with a streaming structural analyzer. No prompts, screenshots, paths, credentials, or raw records are attached.
| Metric | Observed value |
| --- | ---: |
| Rollout JSONL size | 1,905,021,000 bytes (1.774 GiB) |
| JSONL records | 8,068 |
|
compactedrecords | 51 || Bytes in
compactedrecords | 1,751,228,762 (91.93% of file) || Inline
data:imagereferences | 3,426 || Bytes in inline image strings | 1,872,392,983 (98.29% of file) |
| JPEG / PNG references | 3,403 / 23 |
| Exact unique image data URLs | 331 |
| Bytes in one copy of exact unique image data URLs | 116,216,991 (110.83 MiB) |
| Weighted repetition factor | 16.11x |
| Repeated inline-image excess above one exact copy | 1.636 GiB |
| Non-image JSON/text/metadata remainder | about 31.1 MiB |
| Inline audio / other base64 data URLs | 0 / 0 |
The largest single
compactedrecord was 39,309,704 bytes. It contained 69 inline image references totaling 39,140,726 bytes; 68 were unique within that checkpoint. Several later checkpoints had effectively the same inline-image byte total, showing that the same retained media was serialized again into successivereplacement_historysnapshots.The 331 exact unique image data URLs represent about 83.1 MiB of decoded image bytes, but repeated embedding plus base64 expansion turned them into roughly 1.74 GiB of text in the rollout. Removing image strings from the accounting leaves only about 31 MiB for all ordinary JSON, text, and metadata.
This confirms that the abnormal growth is not ordinary conversation text. It is dominated by repeated full
input_imagedata URLs in append-only compaction checkpoints, and the behavior persists on the version combination above.The raw rollout is private and will not be uploaded, but I can provide additional redacted structural counters if maintainers need a specific measurement.
Additional sanitized reproduction from a long-lived Codex Desktop thread. This corroborates the progressive post-compaction floor described here (not an ephemeral/side conversation).
Environment at time of capture
26.727.51351(build6119)codex-cli 0.146.0-alpha.9.2258,400tokensremote_compaction_v2: enabled (stable)Structural measurements
The rollout was about 507 MB and contained 29 compactions across roughly 61 turns. I analyzed only JSON item types, counts, payload lengths, and token-count events; no conversation text, images, paths, or thread identifiers are included here.
The latest compacted
replacement_historycontained:| Metric | Value |
|---|---:|
| Total items | 66 |
| User messages | 62 |
| Developer messages | 3 |
| Compaction item | 1 |
| Retained
input_imageparts | 69 || Image data-URL characters | 23,111,038 |
| User text characters | 96,684 |
| Developer text characters | 41,726 |
The post-compaction baseline rose as historical image attachments/Appshots accumulated:
| Compaction | Retained user msgs | Retained images | First post-compaction input | Free context |
|---:|---:|---:|---:|---:|
| 1 | 4 | 5 | 37,170 | 221,230 |
| 10 | 31 | 40 | 105,626 | 152,774 |
| 20 | 54 | 59 | 143,654 | 114,746 |
| 29 | 62 | 69 | 167,083 | 91,317 |
For compaction 29, input went from
243,772to167,083, so the compaction did reclaim76,689tokens, but the newly installed history still occupied 64.7% of the entire context window immediately after compaction. Later in the same turn it reached193,188, leaving only about65,212tokens.Why this reproduction may be useful
This shows a monotonic, multi-compaction growth pattern rather than only a single near-threshold failure: old assistant/tool history is summarized, while the retained user-message/image prefix grows each time. The post-compaction floor therefore rises from 37k to 167k across the same thread.
A regression test that repeatedly compacts an image-heavy thread should verify that the installed replacement history has a hard media-aware budget and that its post-compaction baseline does not grow monotonically. Counting images as zero in the retained-prefix budget appears inconsistent with their real serialized/context cost; historical media likely needs bounded retention or compact markers/references.
Privacy note: these figures are structural aggregates only; no user content or local identifiers were uploaded.
@zhulijin1991's numbers line up with the code path traced above, item by item.
input_imageparts / 23.1M image chars against 96.7k chars of user text:message_text_token_countmapsContentItem::InputImageto0(codex-rs/core/src/compact_remote_v2.rs:505-525), andtruncate_retained_messages_for_remote_compaction(:478-502) only ever truncates text, so the 64kRETAINED_MESSAGE_TOKEN_BUDGET(:56) never sees the images. That is why the retained prefix is ~65% of the window while the text inside it is trivial.recompute_token_usage(compact.rs:384) nothing compares the result againstauto_compact_token_limit(), so each compaction re-arms rather than converging. This is the strongest evidence so far that fix (b) matters as much as (a) — correcting image accounting alone would slow the climb, not stop it.replacement_historysnapshot (session/mod.rs:3168-3215), while reconstruction only ever consumes the newest one (session/rollout_reconstruction.rs:156-186). 28 of those 29 snapshots are provably dead weight on disk.Your proposed regression test is the same shape as fail-first (2) above, and it is worth asserting both properties: a media-aware budget on the retained prefix, and a post-compaction baseline that does not grow across K compactions. Happy to draft it if a maintainer wants it.
Same happening to me, causing quota to be immediately burned through in quick succession.
This is likely made worse by browser use taking screenshots to show me over time in a long chat.
<img width="1570" height="1280" alt="Image" src="https://github.com/user-attachments/assets/8b65e97e-dff5-43b7-9cbb-85c94bab1499" />
---
I’m seeing this exact issue in my current Codex Desktop thread, and the frequency is getting worse as the conversation grows.
Thread:
019ff63c-32d6-7b43-a936-6712b90b4cd1Embedded CLI:
0.147.0-alpha.6.5Model:
gpt-5.6-solObserved behavior
This thread inherited 30 previous compaction windows and then created another 18 between 13:59 and 16:48.
The context is not being reduced enough after each compaction:
By compaction window 48, approximately 75% of the threshold was already occupied immediately after compaction. Normal agent and tool activity could therefore trigger another compaction within minutes.
Images appear to be the primary cause
The latest compacted replacement history retained:
Retained images increased from 37 at compaction window 31 to 58 at compaction window 48.
Codex’s existing image estimator values those additional 21 images at approximately 38,724 tokens. That explains roughly 92% of the observed 42,032-token increase in the post-compaction baseline.
All 58 retained images represent roughly 106,952 estimated tokens by themselves.
Rollout-file amplification
The rollout is now 568.7 MB.
The same inline images are being copied into successive persisted replacement histories.
Token impact
Over approximately three hours—from compaction window 31 through the end of the captured run—the thread recorded:
Code confirmation
The current implementation applies a 64,000-token retained-message budget:
compact_remote_v2.rs, retained-message budgetIt retains user, developer, system, and qualifying agent messages:
compact_remote_v2.rs, retained-history constructionHowever, images and audio count as zero against that budget:
compact_remote_v2.rs, media excluded from token calculationThose media items are subsequently preserved during truncation:
compact_remote_v2.rs, media preserved during truncationEach compaction checkpoint then persists a clone of the full replacement history:
session/mod.rs, replacement-history persistenceSuggested fix direction
I can provide additional redacted structural measurements if useful. The raw rollout contains private prompts, local paths, tool output, and image data.
Corroborating this with a Windows app-server integration where images are supplied automatically by ComfyUI rather than pasted manually.
Environment at reproduction:
@openai/codex0.145.0-alpha.24gpt-5.6-solcomfyui-mcp; each completed render supplies output previews aslocalImageturn inputsSanitized structural measurements from one affected rollout:
| Metric | Observed value |
| --- | ---: |
| Rollout JSONL size |
19,561,634,635bytes (18.218 GiB) || Persisted inline
input_imagedata URLs |265|| Largest single
compactedrecord |487,265,999bytes || Repeated growth per later compaction | about
487 MB|| Recorded cumulative input tokens | about
46.9M|| Last recorded turn input | about
298Ktokens || Reported context window |
258,400tokens || Stalled app-server process private bytes | about
9.17 GB|Resume took about 61 seconds before the turn stopped producing events and eventually hit the integration's no-activity watchdog. After moving the oversized rollout out of the active sessions directory and starting a fresh durable thread, the same installed CLI/model/auth/bridge completed an exact-response smoke test in about 21 seconds. This isolates the failure to the retained session history rather than provider authentication or transport.
No raw rollout is attached because it contains private prompts, paths, tool output, and images.
The integration-specific amplifier is tracked in artokun/comfyui-mcp#1516: automatic render previews currently have no cumulative per-conversation byte/count budget or oversized-resume guard. That project can add defense in depth, but a local count-only limit cannot repair an existing rollout and does not address the compaction behavior described here.
This looks like old
input_imageblocks remaining in active history after compaction. I built a free, local, read-only Skill that scans for this and creates a clean-session handoff—no uploads or transcript changes: Context Offload.If you try it, a pass/fail result plus Codex version is enough; please don't share the transcript. (Disclosure: I maintain it.)
Your 4.16 GB rollout and 224 repeated compacted records make this more than a token-estimation report: it is a durable history shape with 92 inline images retained in the latest replacement history.
I’m testing whether Codex Rescue’s current bounded diagnostics identify this safely. It cannot solve the repeated auto-compaction loop, strip images, or claim general compaction recovery. It leaves the original rollout untouched, never replays unknown calls, and verification remains fail-closed.
If you still have that session, could you run:
pipx install codex-rescue==0.1.0a3codex-rescue sessionscodex-rescue doctor --latestI’m looking only for sanitized output—please don’t share raw JSONL, images, databases, prompts, repository contents, local paths, or secrets.
https://github.com/shleder/codex-rescue
Related Windows Desktop reproduction on 26.810.41047
I have a smaller but current Windows reproduction that appears to combine the retained-image/compaction behavior described here with the full-context usage amplification discussed in #38495 and #35259.
Environment
26.810.41047OpenAI.Codex_26.810.4967.0258,400Redacted session structure
Local token telemetry during a 2h20m diagnostic window
token_countrecords/model steps: 277These are local rollout counters, not a claim about the private subscription billing ledger. The user's visible allowance nevertheless dropped unusually quickly during the same period.
One user-visible “stuck” turn lasted about 22m49s. The attached screenshot had already been parsed and the agent continued working, but late model steps grew from roughly 120k → 130k → 135k input tokens, mostly cached, while repeatedly processing diagnostic tool output. The turn was eventually interrupted by the user.
I also observed a related image-finalization pattern in two separate threads:
This suggests two interacting risks rather than “image upload always fails”:
Requested fixes align with this issue: externalize image blobs, enforce retained-image count/byte budgets during compaction, leave meaningful post-compaction headroom, and surface/warn on per-thread full-context usage amplification. A quota-review/restoration path for runaway client-generated usage would also help affected users.
I can provide further redacted structural counts if maintainers need them, but I am intentionally not posting private prompts, image content, local paths, or thread identifiers.
@46csvwv274-dev Your current Windows reproduction is useful because it exercises the same retained-image history shape on a newer Desktop build.
If you still have the affected/pre-sanitization rollout, would you be willing to run a bounded local check with Codex Rescue
0.1.0a3?I’m not expecting Rescue to fix the repeated compaction/image-retention loop. I only want to see whether it identifies the persisted boundary safely without modifying the source or making claims about unknown side effects.
Sanitized output only, please — no raw JSONL, images, prompts, repository contents, databases, local paths, IDs, or secrets.
https://github.com/shleder/codex-rescue
@shleder Stop spamming this thread with your SaaS tools please. This isn't an advertisement surface, it's a thread many are watching for official changes.
I can reproduce this on the current macOS desktop build, with slow compaction as the main symptom.
Environment
26.810.520440.147.026.5(25F71)gpt-5.6-sol, high reasoning01a01430-d0a2-7c03-a278-a1d7d201e696Redacted diagnostic evidence
2026-08-18T17:14:33Zand completed at17:17:39Z: 3 minutes 6 seconds.run_auto_compact{reason=ContextLimit phase=MidTurn}.replacement_historyis 5,219,050 serialized bytes with 57 retained items.input_imagevalues containing 5,022,440 inline image/data-URL characters.No task text, screenshots, raw rollout, tool output, local paths, or credentials are attached.
This corroborates the reported cause. Compaction preserves full historical
input_imagepayloads, so later compactions repeatedly process a multi-megabyte multimodal history. In this case, the request succeeds but the latency has become several minutes.Additional current macOS reproduction: rapid compaction loop
I reproduced the same retained-
input_imagefailure on a separate current Codex Desktop task.Environment
0.148.0-alpha.1526.5.1, Apple Silicon258,400tokensPrivacy-safe structural diagnostics
No prompts, screenshots, raw rollout data, private project content, local paths, credentials, or task identifiers are included.
This independently corroborates both reported effects: compacted history retains the full historical image set instead of bounding or externalizing it, and every new checkpoint duplicates those image payloads into the append-only rollout. The resulting task remained technically active but became operationally unusable because compaction no longer created meaningful headroom.
I can provide the privacy-safe
jqmeasurement commands or help design a regression fixture if maintainers want them, and I would be willing to implement a scoped fix if invited under the repository contribution policy.Analysis (community)
@kevindh19 — thanks for the current macOS Desktop reproduction (embedded CLI
0.148.0-alpha.15). That still matches the residual I traced earlier on #33493.Root-cause hypothesis (re-checked 2026-08-20 at
main3b45c2906): remote compaction v2 still treats inlineinput_imageas zero tokens for the retained-history budget, then keeps the image bytes when truncating text. So a successful compact does not create a fixed point: the next turn can still be over budget and compact again.Live path:
message_text_token_countcountsInputText/OutputTextonly;ContentItem::InputImageandInputAudiocontribute 0 (codex-rs/core/src/compact_remote_v2.rs:634-647).truncate_message_text_to_token_budgettruncates text againstremaining, but theInputImage/InputAudioarm unconditionally pushes the content item (:689-691) — images are never dropped by the token budget.retained_input_image_countexists (:545-552) as a metric, not as a drop/GC gate.That is why a current Desktop task can still enter a rapid compaction loop after images are in
replacement_history: compact shrinks text, images stay, usage stays high, compact fires again.This is distinct from #33235 (image inherit on fork into model context). Same payload class, different copy event (compaction retain vs fork inherit).
No unsolicited PR from me. If maintainers want a fail-first test, the existing
build_v2_compacted_history_counts_retained_input_images(:914) is the right fixture to extend: after compact, retained images must either count toward the budget or be stripped.Happy to refine the HEAD pin if a newer compact path landed after
3b45c2906.Current macOS build still reproduces this, with per-turn latency
This is still happening on the current installed Codex Desktop build. It is not only damage left behind by an older build.
Environment:
26.818.22352(build6872)0.148.0-alpha.2126.6.1(25G76), Apple Silicon2026-08-19with embedded CLI0.148.0-alpha.15The app updated and restarted on August 20. After that restart, the affected task produced two more compacted records, at
21:52:58Zand23:21:09Z. Each retained the same nineinput_imageitems insidepayload.replacement_history, totaling16,755,582inline image characters. This confirms that the current build is still creating the bad state.A privacy-safe structural scan of the task now shows:
205,621,888bytes5,0187input_imagenodes retained across completed replacement histories:4063,405,544characters43(126,975,157characters), left untouched by the scanThe user-visible symptom is latency on every turn, not only during compaction. New tasks on the same installation respond in roughly 1–2 seconds, while this task takes 40+ seconds for trivial prompts. A recent short acknowledgement took
68.493s; another ordinary turn took195.155s.A schema-aware dry run found that the affected task can be repaired by replacing only
input_imagenodes inside completedpayload.replacement_history, while preserving current/uncompacted images and backing up the rollout. No cleanup was applied before taking these measurements.The product needs both parts:
I am not attaching the rollout because it contains private prompts, screenshots, local paths, and tool output.
I have a second reproduction matching this issue, this time in Codex CLI on Windows.
Environment
gpt-5.6-solmaxauto258,40001a01907-f597-7d91-a8c1-e7cb0ce7d5d00.148.0/statusreport CLI0.149.0for the same resumed session. The rollout does not record the client version for every resume.User-visible behavior
The TUI repeatedly displayed
Context compactedwithout completing useful work:Workingafter 6m33s.Workingafter 6m42s.Conversation interrupted./statusthen showed0% left (285K used / 258K)around another compaction.<img width="1130" height="267" alt="Image" src="https://github.com/user-attachments/assets/e03f0f81-5fe3-44ec-a77e-f8b9a11d80a0" />
<img width="1112" height="297" alt="Image" src="https://github.com/user-attachments/assets/18defe3f-c1d1-4745-89d5-c15129371ff5" />
<img width="1115" height="654" alt="Image" src="https://github.com/user-attachments/assets/4d236ba6-c3f0-4e0d-a2f9-f5d2f3643409" />
<img width="482" height="235" alt="Image" src="https://github.com/user-attachments/assets/9efb0692-e536-4e87-b5cb-51a541f057d0" />
Privacy-safe rollout measurements
These measurements inspect record structure, counts and sizes only. I am not attaching the raw rollout because it contains private prompts, paths, tool output and images.
Measured at compaction 60 on
2026-08-21T16:21:45Z:| Metric | Observed value |
| --- | ---: |
| Total compactions | 60 |
| Rollout JSONL size | 667.23 MiB |
| Size occupied by compaction records | 543.48 MiB (81.5%) |
| Latest compacted record | 12.13 MiB |
| Latest
replacement_history| 317 items || Retained
input_imagenodes | 131 || Distinct exact image payloads in latest history | 130 |
| Retained image data-URL characters | 12,502,222 |
| Retained text characters | 102,782 |
| Image share of retained image + text characters | approximately 99.2% |
| Image occurrences across all 60 compaction snapshots | 5,706 |
| Distinct exact image payloads across those snapshots | 130 |
| Average reserialization per distinct image | 43.9 times |
| Image MIME type | all
image/png|This is not mainly duplicate images within a single history: the latest history has 131 image nodes representing 130 exact payloads. The major amplification is the same retained images being copied into each subsequent compacted record.
Rapid compaction bursts
Two separate loops occurred:
There were zero function / tool calls between the compactions in either burst. Individual intervals were often only about 32-72 seconds.
The nearest token telemetry around compaction 42 changed from
226,678before compaction to295,687after it. Subsequent post-compaction totals remained around279K-296K.For compaction 60:
299,346299,414258,400Across the rollout, 31 of 60 first post-compaction token samples were already at or above the full
258,400context window. This explains why compaction immediately re-armed without a meaningful new turn.The
/statusUI and rollout telemetry use somewhat different totals (285Kversus299,414in the latest observation), but both independently place the session above the258Kwindow immediately after compaction.Multi-agent context
The run spawned seven subagents and contained substantial collaboration activity before the loop. That may have accelerated history growth, but the actual rapid-compaction windows contained no agent or other tool calls, so multi-agent activity does not appear to be the immediate loop mechanism.
The rollout also contains six
turn_abortedevents overall, and the TUI displayedConversation interruptedduring the later burst.Interpretation
This appears to reproduce the mechanism described in this issue:
input_imagedata URLs.The separate reasoning-accounting issue in #39767 may influence when the first compaction is triggered on
gpt-5.6-sol, but it cannot account for the retained 12.5 million image characters or the repeated copying of 130 image payloads. This rollout therefore provides independent evidence for the retained-image problem reported here.I can provide additional redacted structural measurements if maintainers need them.
Additional current macOS reproduction: gradual image accumulation causes per-turn compaction thrash
Privacy-safe structural scan of another affected Codex Desktop rollout. This case accumulated screenshots over a long-running thread rather than attaching one large batch.
Environment
0.148.0-alpha.9gpt-5.6-sol,xhigh353,400tokensStructural measurements
18input_imagenodes in latest replacement history:2949,162,318124items, approximately49,367,092serialized characters17and18retained the exact same ordered image payload setcontext_compactedevents occurred5m46sapart inside one unfinished turnToken sequence
247,329.309,488 / 353,400input tokens.330,134input tokens.268,560.309,619input tokens.The second compaction therefore produced only about
20.5Knet prompt reduction relative to the last pre-compaction call. The retained image floor immediately restored the prompt to roughly87.6%of the effective window.No rollback or retry duplicated the UI event: the rollout records distinct compaction window IDs and incremented
window_numberfrom17to18.This matches the reported mechanism: raw historical images survive the retained-message budget, compaction removes mostly text/tool history, and the next ordinary turn immediately approaches the threshold again. User-visible result is several minutes of compaction latency per question and an operationally unusable task.
No raw rollout, prompts, paths, images, tool outputs, or thread IDs are included.
Independent macOS/Desktop reproduction on a current build, with two separate rollouts reaching 25+ GB.
Environment
26.818.31338(build6892)codex-cli 0.146.026.6.2(25G83)gpt-5.6-solopenai/codex@4f39251a010a8bd7d692d25fb33832ff06f1635aPrivacy-safe structural evidence
Affected rollout A:
28,091,832,834bytescompactionrecords:261context_compactedevents:261input_imageoccurrences:14,74314,741108,795,437bytes before compaction and80,273,208bytes after compaction.581,308tokens against an auto-compaction limit of244,800.token_limit_reached=true, repeatedly followed byrun_auto_compactwithreason=ContextLimitandphase=MidTurn.Affected rollout B, from a separate earlier image-heavy ebook task on the same machine:
26,467,249,845bytes37,991,677bytes and 51 token-limit events around245k-251ktokens against the same244,800limit.ContextLimit/MidTurnauto-compaction pattern.No raw rollout, prompts, paths, images, or base64 payloads are attached because they contain private data.
Current source trace
Current
mainstill matches the observed failure mode:codex-rs/core/src/compact_remote_v2.rs::message_text_token_countchargesInputImageandInputAudioas zero text tokens.truncate_message_text_to_token_budgetpreserves those media content items.build_v2_compacted_historyretains user messages and reports the retained image count.codex-rs/core/src/session/turn.rsruns mid-turn auto-compaction and immediatelycontinues the turn. There is no local consecutive-attempt ceiling or check that the compacted context actually fell materially below the threshold before another pass.This makes the failure self-amplifying: images survive compaction, the resulting context stays over the limit, another full image-bearing request/history is persisted, and the append-only rollout grows roughly by the repeated payload size on each pass.
Suggested safety fix
This also corroborates the storage amplification reported in #33735, but the non-decreasing mid-turn compaction loop appears to be the immediate trigger for the two 25+ GB files above.
---
This issue or PR was generated by LazyCodex.
Tag: lazycodex-generated
FYI Tibo finally posted about this bug: https://x.com/thsottiaux/status/2091407991736332689?s=20
“Dude, we're like on TV right now!”