Local compaction v2 retains unbounded input_image payloads, causing repeated auto-compaction

Open 💬 24 comments Opened Jul 16, 2026 by snrui
💡 Likely answer: A maintainer (github-actions[bot], contributor) responded on this thread — see the highlighted reply below.

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:

  1. The post-compaction prompt remains close to the context threshold, causing immediate re-compaction on subsequent turns.
  2. 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

  1. Use a long-running Codex Desktop thread containing multiple pasted screenshots or image inputs.
  2. Continue until local/remote-v2 compaction occurs.
  3. Inspect the newest type: "compacted" rollout record.
  4. Confirm that payload.replacement_history retains user messages containing input_image data URLs.
  5. Send a short follow-up question.
  6. Observe another automatic compaction even though the previous compaction just completed.
  7. 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.rs
  • message_text_token_count
  • truncate_message_text_to_token_budget
  • truncate_retained_messages_for_remote_compaction

Possible fix direction

For active compacted history:

  1. Give retained images a conservative estimated token cost, using the existing image-size/detail estimator where possible.
  2. Add explicit caps for retained image count and/or encoded bytes.
  3. Keep the newest images first and replace older images with a lightweight placeholder once the image budget is exhausted.
  4. 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_image problem, closed after the compaction architecture changed; this report reproduces the behavior on the newer local compaction path.
  • #24550: large inline images in compacted replacement_history cause 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.

View original on GitHub ↗

24 Comments

github-actions[bot] contributor · 1 month ago

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

  • #33235

Powered by Codex Action

ignatremizov · 1 month ago

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_image parts | 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:

  1. Immediately after compaction, local recomputation emitted total_tokens=294,141, input_tokens=0, which rendered as 17% context remaining.
  2. The first real post-compaction model response reported 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 the InputImage and 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 main now 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_history selects 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. InputImage and InputAudio contribute 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_image already 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_usage also estimates only base instructions plus installed history and marks the result as input_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_history as 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.

  1. Add a shared helper that replaces retained InputImage and InputAudio parts before retained-message budgeting.
  • For the canonical <image ... path="...">, InputImage, </image> sequence, preserve the existing path wrapper and replace only InputImage with bounded text such as Image bytes removed after compaction; use view_image with the path above if needed.
  • This avoids parsing or rewriting path strings and retains a useful model-callable reference.
  • For media without a durable source reference, use a bounded unavailable marker rather than retaining the data URL.
  1. Apply that helper inside build_v2_compacted_history before truncate_retained_messages_for_remote_compaction.
  • The reference/marker text is then charged against the existing 64k budget.
  • Message order, existing path wrappers, IDs, phases, and passthrough metadata remain intact.
  • The newly installed replacement history contains no raw inline media.
  1. Add a synchronous historic-rollout repair pass at the shared resume/fork reconstruction boundary, before any model request.
  • reconstruct_history_from_rollout already distinguishes the latest base replacement_history from the newer rollout suffix.
  • Sanitize only that base replacement history. Do not sanitize media in the post-checkpoint suffix because it has not yet been summarized.
  • Preserve adjacent local-image path wrappers and replace only raw media parts.
  • Return a repair report containing whether the history changed, omitted counts/bytes, and the repaired checkpoint metadata.
  • Make the transformation deterministic and idempotent.
  1. Persist one durable repaired checkpoint when the reconstruction pass changes history.
  • On resume, append a sanitized replacement-history checkpoint with the same window number/IDs and compaction payload; this is a representation migration, not a new context window.
  • On fork, persist the sanitized checkpoint only in the child rollout and leave the parent byte-for-byte unchanged.
  • Add an explicit rollout migration/media-policy version so a second resume does not append an equivalent checkpoint.
  • Persist the repaired checkpoint before accepting the first user/model turn, so a crash cannot leave in-memory and future-resume semantics divergent.
  1. Keep a defensive recovery pass in run_remote_compact_v2_attempt for already-running sessions upgraded in place or histories that bypass cold reconstruction.
  • The cloned history is available before for_prompt() constructs the compaction request:

https://github.com/openai/codex/blob/0fb559f0f6e231a88ac02ea002d3ecd248e2b515/codex-rs/core/src/compact_remote_v2_attempt.rs#L41-L85

  • Find the most recent ResponseItem::Compaction / ContextCompaction boundary.
  • Replace raw media only in items at or before that boundary because those items were already summarized by the previous checkpoint.
  • Preserve adjacent local-image path wrappers so historical images remain reopenable.
  • Keep media added after the boundary so the current compaction model can inspect and summarize it.
  • Record the sanitized history as the actual trace input so trace data matches the model request.
  1. After the compaction response succeeds, sanitize all retained raw media before installing the next replacement history while keeping available references. This means first-compaction media is visible to the compactor once, but is not replayed indefinitely afterward and can still be reopened on demand.
  2. Treat physical rollout cleanup as a separate non-blocking vacuum after successful reconstruction.
  • The synchronous repair fixes active context but an appended checkpoint does not reclaim bytes from superseded records.
  • Never rewrite a multi-gigabyte rollout on the resume critical path.
  • Under an exclusive rollout-writer lock, stream the source into a same-directory temporary file and strip raw media only from superseded CompactedItem.replacement_history snapshots.
  • Preserve original user/tool response records, ordering, IDs, timestamps, source-path text, and opaque encrypted content byte-for-byte.
  • Validate every output JSONL record, fsync the file and directory, retain a recoverable backup, and use an atomic replacement. Any interruption before the final rename must leave the original usable.
  • Fork cleanup must never rewrite the parent rollout.
  1. Complete the reverse/lazy checkpoint loader already anticipated by the reconstruction comments. Until then, current eager loading must still parse the old large file before the repair pass can run; the repaired checkpoint fixes model context immediately, while the vacuum is what restores current-startup and disk behavior.
  2. Extend existing compaction/reconstruction analytics with omitted_image_count, omitted_audio_count, omitted inline-media bytes, repair-applied/version, vacuum bytes before/after, and vacuum failure status. Keep retained_image_count for verifying that the installed replacement reaches zero under this policy.
  3. Treat environment ownership explicitly.
  • Model-generated view_image paths remain strings at the tool boundary, but resolution should continue through the selected environment and its filesystem.
  • When the source is not in the default environment, retain enough provenance to supply environment_id.
  • Do not assume that a host path is readable by a remote executor.
  1. For pathless pasted/data-URL images, a later stage can materialize content once into an environment-owned, content-addressed media store and retain a stable reference. If that storage contract is not available, the safe first-stage fallback is an unavailable marker, not inline bytes.
  2. If product requirements later demand raw post-compaction media retention, add it as a separately reviewed bounded policy using the existing model-visible estimator plus explicit count/byte caps. It should not remain an accidental zero-cost exception to a text budget.

Acceptance criteria

  • A first compaction request can include images from the current window so the compaction model can summarize them.
  • The installed replacement_history after compaction contains no raw InputImage or InputAudio payloads.
  • A later compaction request does not replay media that was already covered by the preceding compaction output.
  • A cold resume from a legacy image-heavy checkpoint sanitizes the base replacement history before the first model request while preserving unsummarized suffix media.
  • A resumed thread appends exactly one versioned repaired checkpoint without advancing the context-window identity; a second resume is a no-op.
  • A fork persists sanitized inherited context only in the child and leaves the parent rollout unchanged.
  • Local-image messages preserve their existing source paths, ordering, metadata, and bounded replacement text.
  • A post-compaction model turn can call view_image with a retained path and successfully reload the image when that source remains available.
  • Pathless or missing sources degrade to an explicit unavailable marker without reintroducing inline bytes.
  • The existing image-preservation tests are replaced with assertions that reference/marker text is budgeted and raw media is absent.
  • An integration test performs two compactions around image inputs and verifies that the second request does not contain prior-window data URLs.
  • Remote-executor coverage verifies that a retained non-default-environment reference supplies the correct environment_id; a host-only path is never treated as an executor path.
  • A regression test with 100+ image messages verifies that post-compaction history stays well below the auto-compaction threshold and a short follow-up does not immediately compact again.
  • A vacuum test starts from a multi-checkpoint legacy rollout, verifies substantial byte reclamation, and proves that reconstructed model context and transcript-visible original image events are unchanged.
  • Vacuum interruption/failure tests verify that the original rollout remains valid and selected until the replacement has been fully validated and atomically installed.
  • Encrypted compaction/reasoning fields remain byte-identical through repair and vacuum.
  • InputAudio receives 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.

dorukardahan · 1 month ago

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_image items. All had detail: 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 main at 808d3c2702ce8eae007c457aa930e7c3b68dd5f6 still 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.

romainsimon · 1 month ago

Adding a corroborating data point for this on macOS, because the impact went beyond disk and into a very large token bill.

Environment

  • ChatGPT desktop, version 26.715.72359 (build 5718)
  • Codex framework 150.0.7871.124
  • macOS (Apple Silicon), 460 GB disk

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)

  • Sessions directory reached ~81 GB.
  • 117 session files were over 200 MB, totaling ~59 GB.
  • Largest single session file: 1.5 GB; several others 700 MB to 1.1 GB.
  • In a single day, in one project, 101 session files exceeded 200 MB across ~81 distinct session ids, totaling ~46 GB. Many were near-identical in size and created seconds to minutes apart (e.g. four ~826 MB files within an 11-minute window), i.e. repeated full copies of the same conversation rather than distinct sessions.
  • By contrast, 451 normal (text-only) session files were under 50 MB and totaled ~3 GB combined.

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)

  • Store images out of line as separate blobs referenced by id, never inline base64 in the rollout, and never re-embed them in compaction/replacement_history records.
  • On resume/compaction, append or reference rather than writing a full duplicate copy of the whole session.
  • Add a retention/size cap and garbage-collection for old rollout copies, with a warning when a session grows abnormally large.

Workaround
Fully quit the ChatGPT/Codex app (so no session files are held open), then delete the redundant oversized rollout copies from ~/.codex/sessions while preserving the normal small threads. That reclaimed ~50 GB in my case.

bbingz · 1 month ago

Analysis (community)

Residual analysis for #33493 (compaction v2 retains unbounded input_image payloads → 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_history snapshot to an append-only rollout with no GC. Verified at HEAD 4c43465133:

1. Images are counted as 0 tokens and budget-exempt — exactly as reported. In the v2 truncation pass, message_text_token_count maps ContentItem::InputImage and InputAudio to 0 (codex-rs/core/src/compact_remote_v2.rs:505-525), and truncate_retained_messages_for_remote_compaction (compact_remote_v2.rs:478-502) only ever truncates text via truncate_message_text_to_token_budget — an image-only message sails through the 64k RETAINED_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_usage only re-estimates (compact.rs:384); nothing compares the result against auto_compact_token_limit() (= context_window * 9/10, protocol/src/openai_models.rs:459-470). Pre-turn compaction fires on every turn when token_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.rs has 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) plus SUMMARY_PREFIX + summary_suffix where 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 in build_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_history persists RolloutItem::Compacted carrying the complete replacement_history, plus full WorldState and TurnContext items (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 first replacement_history it meets and replays only the suffix). Every older snapshot is provably dead weight, and there is no GC/retention pass anywhere in rollout/ or core/. 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 ContextWindowExceeded the local path removes ONE oldest history item, resets retries = 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 terminal context_length_exceeded sibling of this issue.

Ranked fix outline (all OSS, codex-rs):

  • (a) Count input_image at a real token estimate (or a conservative per-image constant) in message_text_token_count, and let truncate_retained_messages_for_remote_compaction drop/placeholder oldest images when over budget — closes the reported trigger inside the code the v2 fix introduced.
  • (b) Post-compaction fits-check: after 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 the turn.rs:394 assumption with a structural guarantee.
  • (c) Cap the chained summary (e.g. 4–8k tokens) in build_compacted_history.
  • (d) Re-fire guard: skip PreTurn auto-compact when the last compaction was in the current window, no new turn occurred, and usage hasn't grown past the post-compaction baseline.
  • (e) Checkpoint-chain GC: since reconstruction only ever reads the newest Compacted, a best-effort pruner (temp-file + rename, modeled on the existing cold-compression worker in rollout/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.
  • (f) Cap the trim-1-retry loop with a constant MAX_TRIM_PASSES and 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_image and <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 live Compacted snapshot 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.

fujiwarakasei · 29 days ago

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

  • ChatGPT/Codex Desktop: 26.721.41059 (build 5848)
  • Affected rollout metadata: cli_version 0.145.0-alpha.18
  • Currently installed CLI: codex-cli 0.146.0
  • macOS: 26.5.2 (25F84), Apple Silicon
  • Model: gpt-5.6-sol

Sanitized 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 |
| compacted records | 51 |
| Bytes in compacted records | 1,751,228,762 (91.93% of file) |
| Inline data:image references | 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 compacted record 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 successive replacement_history snapshots.

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_image data 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.

zhulijin1991 · 23 days ago

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

  • Codex Desktop: 26.727.51351 (build 6119)
  • Embedded CLI: codex-cli 0.146.0-alpha.9.2
  • macOS / arm64
  • Reported context window: 258,400 tokens
  • remote_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_history contained:

| Metric | Value |
|---|---:|
| Total items | 66 |
| User messages | 62 |
| Developer messages | 3 |
| Compaction item | 1 |
| Retained input_image parts | 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,772 to 167,083, so the compaction did reclaim 76,689 tokens, but the newly installed history still occupied 64.7% of the entire context window immediately after compaction. Later in the same turn it reached 193,188, leaving only about 65,212 tokens.

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.

bbingz · 22 days ago

@zhulijin1991's numbers line up with the code path traced above, item by item.

  • 69 retained input_image parts / 23.1M image chars against 96.7k chars of user text: message_text_token_count maps ContentItem::InputImage to 0 (codex-rs/core/src/compact_remote_v2.rs:505-525), and truncate_retained_messages_for_remote_compaction (:478-502) only ever truncates text, so the 64k RETAINED_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.
  • The monotonic floor (37k → 105k → 143k → 167k over 29 compactions) is the missing fixed-point guarantee: after recompute_token_usage (compact.rs:384) nothing compares the result against auto_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.
  • 507MB across 29 compactions is item (e): every compaction appends a complete replacement_history snapshot (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.

iwasrobbed · 15 days ago

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-6712b90b4cd1
Embedded CLI: 0.147.0-alpha.6.5
Model: gpt-5.6-sol

Observed behavior

This thread inherited 30 previous compaction windows and then created another 18 between 13:59 and 16:48.

  • Last ten compaction intervals averaged 6.23 minutes.
  • Last five averaged 5.8 minutes.
  • The final three intervals were 6:07, 5:16, and 3:59.

The context is not being reduced enough after each compaction:

  • After compaction window 31: 141,659 input tokens
  • After compaction window 48: 183,691 input tokens
  • Increase in the post-compaction baseline: 42,032 tokens, or 29.7%
  • Automatic-compaction threshold: approximately 244,800 tokens
  • Post-compaction headroom fell from 103,141 to only 61,109 tokens.

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:

  • 58 high-detail images
  • 102 user messages
  • 60 inter-agent messages
  • 3 developer messages
  • 166 retained items in total
  • 15.43 million inline image/data-URL characters

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 48 persisted compaction records occupy 446.6 MB.
  • Compaction records therefore account for 78.5% of the file.
  • The latest individual compaction record is 15.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:

  • 142.89 million additional input tokens
  • 139.25 million cached input tokens
  • 289,131 output tokens

Code confirmation

The current implementation applies a 64,000-token retained-message budget:

compact_remote_v2.rs, retained-message budget

It retains user, developer, system, and qualifying agent messages:

compact_remote_v2.rs, retained-history construction

However, images and audio count as zero against that budget:

compact_remote_v2.rs, media excluded from token calculation

Those media items are subsequently preserved during truncation:

compact_remote_v2.rs, media preserved during truncation

Each compaction checkpoint then persists a clone of the full replacement history:

session/mod.rs, replacement-history persistence

Suggested fix direction

  1. Charge retained images and audio against the compaction budget using the existing modality-aware estimator.
  2. Add explicit retained-media token, count, and encoded-byte caps.
  3. Keep only the newest relevant images and replace older ones with lightweight markers after summarization.
  4. Ensure compaction leaves meaningful headroom below the next automatic-compaction threshold.
  5. Deduplicate persisted media instead of copying inline base64 data into every checkpoint.
  6. Add a regression test with many mixed text/image messages and verify that a short follow-up does not immediately trigger another compaction.

I can provide additional redacted structural measurements if useful. The raw rollout contains private prompts, local paths, tool output, and image data.

JusticeWay · 15 days ago

Corroborating this with a Windows app-server integration where images are supplied automatically by ComfyUI rather than pasted manually.

Environment at reproduction:

  • Windows 11 x64
  • bundled @openai/codex 0.145.0-alpha.24
  • gpt-5.6-sol
  • app-server driven by comfyui-mcp; each completed render supplies output previews as localImage turn inputs

Sanitized structural measurements from one affected rollout:

| Metric | Observed value |
| --- | ---: |
| Rollout JSONL size | 19,561,634,635 bytes (18.218 GiB) |
| Persisted inline input_image data URLs | 265 |
| Largest single compacted record | 487,265,999 bytes |
| Repeated growth per later compaction | about 487 MB |
| Recorded cumulative input tokens | about 46.9M |
| Last recorded turn input | about 298K tokens |
| Reported context window | 258,400 tokens |
| 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.

zdayang · 14 days ago

This looks like old input_image blocks 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.)

shleder · 14 days ago

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.0a3
codex-rescue sessions
codex-rescue doctor --latest

I’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

46csvwv274-dev · 13 days ago

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

  • Codex Desktop release: 26.810.41047
  • MSIX package: OpenAI.Codex_26.810.4967.0
  • Windows 11 x64
  • Effective context window reported by the rollout: 258,400

Redacted session structure

  • Rollout JSONL: 24.33 MB
  • Image-bearing user turns: 9
  • Compaction records: 4
  • The four compaction records account for approximately 11.36 MB of the rollout
  • Earlier image turns could complete, so image input is not a deterministic trigger. However, retained image payloads appear to make every later model/tool step substantially more expensive.

Local token telemetry during a 2h20m diagnostic window

  • token_count records/model steps: 277
  • Input tokens: 33,320,854
  • Cached input: 31,913,344
  • Uncached input: 1,407,510
  • Output tokens: 109,544
  • Reasoning output: 49,646

These 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:

  • A 3-image generation thread with no reference image: all image-generation calls ended, but the task did not finalize normally.
  • A 3-step referenced-image edit thread: all image-generation calls ended, but the final turn was aborted/missing its normal completion.
  • A single-image control thread completed successfully.

This suggests two interacting risks rather than “image upload always fails”:

  1. inline/retained image payloads and repeated compaction inflate replayable session state;
  2. later tool/model cycles repeatedly ingest the large cached prefix, rapidly consuming usage while the UI appears stuck.

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.

shleder · 13 days ago

@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?

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

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

iwasrobbed · 13 days ago

@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.

Klohto · 9 days ago

I can reproduce this on the current macOS desktop build, with slow compaction as the main symptom.

Environment

  • Codex Desktop app: 26.810.52044
  • Codex CLI: 0.147.0
  • macOS: 26.5 (25F71)
  • Model: gpt-5.6-sol, high reasoning
  • Affected task ID: 01a01430-d0a2-7c03-a278-a1d7d201e696

Redacted diagnostic evidence

  • The latest auto-compaction began at 2026-08-18T17:14:33Z and completed at 17:17:39Z: 3 minutes 6 seconds.
  • The request ran through run_auto_compact{reason=ContextLimit phase=MidTurn}.
  • Context immediately before compaction was 251,651 / 258,400 tokens.
  • The logs show a normal Responses WebSocket compaction request. There was no retry, authentication failure, or transport error during this compaction.
  • The newest compacted replacement_history is 5,219,050 serialized bytes with 57 retained items.
  • It retains 10 input_image values containing 5,022,440 inline image/data-URL characters.
  • Three retained user messages account for almost the entire payload: approximately 1.60 MB, 2.18 MB, and 1.25 MB.
  • Compacted replacement-history size grew across the task from 14,507 bytes to 1,756,504, 3,956,864, 5,212,655, and finally 5,219,050 bytes.
  • Recent compactions take about 2:45 to 5:32. The latest took 3:06.
  • The append-only rollout is currently 57,795,192 bytes.

No task text, screenshots, raw rollout, tool output, local paths, or credentials are attached.

This corroborates the reported cause. Compaction preserves full historical input_image payloads, so later compactions repeatedly process a multi-megabyte multimodal history. In this case, the request succeeds but the latency has become several minutes.

kevindh19 · 8 days ago

Additional current macOS reproduction: rapid compaction loop

I reproduced the same retained-input_image failure on a separate current Codex Desktop task.

Environment

  • Codex Desktop origin
  • Embedded Codex CLI: 0.148.0-alpha.15
  • macOS 26.5.1, Apple Silicon
  • Model context window reported by the rollout: 258,400 tokens

Privacy-safe structural diagnostics

  • Task created less than one day before the failure.
  • Rollout JSONL reached 885,073,090 bytes.
  • The task contained 46 image inputs.
  • It had 29 compaction records at measurement time.
  • Compaction records alone accounted for approximately 831.9 MB of the rollout.
  • Each recent compacted payload was approximately 33.6 MB and retained all 46 images.
  • The task entered a burst of 9 compactions within five minutes, including compactions separated by roughly 20–35 seconds.
  • A real request immediately before compaction reported approximately 246,520 / 258,400 tokens.
  • The provisional post-compaction state remained around 210,078 / 258,400 tokens, leaving the task close enough to the threshold that another ordinary tool/model step immediately triggered compaction again.

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 jq measurement 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.

bbingz · 8 days ago

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 main 3b45c2906): remote compaction v2 still treats inline input_image as 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:

  1. message_text_token_count counts InputText/OutputText only; ContentItem::InputImage and InputAudio contribute 0 (codex-rs/core/src/compact_remote_v2.rs:634-647).
  2. truncate_message_text_to_token_budget truncates text against remaining, but the InputImage/InputAudio arm unconditionally pushes the content item (:689-691) — images are never dropped by the token budget.
  3. retained_input_image_count exists (: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.

markmdev · 7 days ago

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:

  • Codex Desktop: 26.818.22352 (build 6872)
  • Embedded CLI: 0.148.0-alpha.21
  • macOS 26.6.1 (25G76), Apple Silicon
  • Affected task started on 2026-08-19 with embedded CLI 0.148.0-alpha.15

The app updated and restarted on August 20. After that restart, the affected task produced two more compacted records, at 21:52:58Z and 23:21:09Z. Each retained the same nine input_image items inside payload.replacement_history, totaling 16,755,582 inline image characters. This confirms that the current build is still creating the bad state.

A privacy-safe structural scan of the task now shows:

  • rollout size: 205,621,888 bytes
  • JSONL records: 5,018
  • completed compactions: 7
  • input_image nodes retained across completed replacement histories: 40
  • retained compacted-image payload: 63,405,544 characters
  • images outside completed compaction history: 43 (126,975,157 characters), left untouched by the scan

The 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 took 195.155s.

A schema-aware dry run found that the affected task can be repaired by replacing only input_image nodes inside completed payload.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:

  1. Stop retaining inline image payloads in new compacted replacement histories.
  2. Provide a safe migration or built-in repair action for tasks already affected.

I am not attaching the rollout because it contains private prompts, screenshots, local paths, and tool output.

laffo16 · 6 days ago

I have a second reproduction matching this issue, this time in Codex CLI on Windows.

Environment

  • Platform: Windows 10.0.19044 x64
  • Terminal: Windows Terminal / PowerShell 7.6.5
  • Model: gpt-5.6-sol
  • Reasoning effort: max
  • Summaries: auto
  • Collaboration: multi-agent v2, default mode
  • Subscription: ChatGPT Pro
  • Effective context window: 258,400
  • Session ID: 01a01907-f597-7d91-a8c1-e7cb0ce7d5d0
  • Session creation metadata records CLI 0.148.0
  • The current installation and /status report CLI 0.149.0 for the same resumed session. The rollout does not record the client version for every resume.

User-visible behavior

The TUI repeatedly displayed Context compacted without completing useful work:

  • One screenshot shows three consecutive compactions while still Working after 6m33s.
  • A subsequent screenshot shows four consecutive compactions while still Working after 6m42s.
  • The UI later displayed Conversation interrupted.
  • /status then showed 0% 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_image nodes | 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:

  1. Compactions 42-51: 10 compactions in 8m09s.
  2. Compactions 52-60: 9 compactions in 7m22s.

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,678 before compaction to 295,687 after it. Subsequent post-compaction totals remained around 279K-296K.

For compaction 60:

  • Before: 299,346
  • After: 299,414
  • Context window: 258,400

Across the rollout, 31 of 60 first post-compaction token samples were already at or above the full 258,400 context window. This explains why compaction immediately re-armed without a meaningful new turn.

The /status UI and rollout telemetry use somewhat different totals (285K versus 299,414 in the latest observation), but both independently place the session above the 258K window 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_aborted events overall, and the TUI displayed Conversation interrupted during the later burst.

Interpretation

This appears to reproduce the mechanism described in this issue:

  1. Compaction retains the full historical input_image data URLs.
  2. Images dominate the resulting replacement history.
  3. The post-compaction state can remain above the entire context window.
  4. Auto-compaction therefore starts again without meaningful intervening work.
  5. Every compaction persists another approximately 12 MiB copy of substantially the same image history.

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.

garpunzel · 6 days ago

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

  • Codex Desktop origin
  • Rollout creation metadata: embedded CLI 0.148.0-alpha.9
  • Model: gpt-5.6-sol, xhigh
  • Effective context window: 353,400 tokens

Structural measurements

  • Completed compaction records: 18
  • Retained input_image nodes in latest replacement history: 29
  • Retained inline image/data-URL characters: 49,162,318
  • Latest replacement history: 124 items, approximately 49,367,092 serialized characters
  • Compaction windows 17 and 18 retained the exact same ordered image payload set
  • Those two context_compacted events occurred 5m46s apart inside one unfinished turn

Token sequence

  1. First compaction provisional total: 247,329.
  2. First real post-compaction model call: 309,488 / 353,400 input tokens.
  3. Normal follow-up work grew the prompt to 330,134 input tokens.
  4. Second compaction provisional total: 268,560.
  5. First real post-second-compaction call: 309,619 input tokens.

The second compaction therefore produced only about 20.5K net prompt reduction relative to the last pre-compaction call. The retained image floor immediately restored the prompt to roughly 87.6% of the effective window.

No rollback or retry duplicated the UI event: the rollout records distinct compaction window IDs and incremented window_number from 17 to 18.

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.

ccomaeng · 6 days ago

Independent macOS/Desktop reproduction on a current build, with two separate rollouts reaching 25+ GB.

Environment

  • ChatGPT/Codex Desktop: 26.818.31338 (build 6892)
  • Standalone CLI: codex-cli 0.146.0
  • macOS: 26.6.2 (25G83)
  • Model: gpt-5.6-sol
  • Current upstream source checked at openai/codex@4f39251a010a8bd7d692d25fb33832ff06f1635a

Privacy-safe structural evidence

Affected rollout A:

  • File size: 28,091,832,834 bytes
  • compaction records: 261
  • context_compacted events: 261
  • input_image occurrences: 14,743
  • Inline JPEG data URLs: 14,741
  • Retained runtime logs show 141 repeated requests with approximately 108,795,437 bytes before compaction and 80,273,208 bytes after compaction.
  • Observed usage remained approximately 581,308 tokens against an auto-compaction limit of 244,800.
  • 70 observed post-sampling checks reported token_limit_reached=true, repeatedly followed by run_auto_compact with reason=ContextLimit and phase=MidTurn.

Affected rollout B, from a separate earlier image-heavy ebook task on the same machine:

  • File size: 26,467,249,845 bytes
  • Retained runtime logs show 105 repeated request bodies around 37,991,677 bytes and 51 token-limit events around 245k-251k tokens against the same 244,800 limit.
  • It exhibited the same repeated ContextLimit / MidTurn auto-compaction pattern.

No raw rollout, prompts, paths, images, or base64 payloads are attached because they contain private data.

Current source trace

Current main still matches the observed failure mode:

  • codex-rs/core/src/compact_remote_v2.rs::message_text_token_count charges InputImage and InputAudio as zero text tokens.
  • truncate_message_text_to_token_budget preserves those media content items.
  • build_v2_compacted_history retains user messages and reports the retained image count.
  • codex-rs/core/src/session/turn.rs runs mid-turn auto-compaction and immediately continues 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

  1. Budget retained images by realistic vision-token cost, encoded bytes, and image count rather than a near-zero message floor.
  2. Replace older reopenable local images with lightweight path/content-addressed references at a successful compaction boundary.
  3. Add a progress invariant and a hard ceiling for consecutive auto-compactions in one turn. If post-compaction context does not fall sufficiently, stop and surface a recoverable warning instead of looping.
  4. Add a rollout byte-growth guard and regression coverage proving that mixed text/image history cannot produce repeated non-decreasing compactions.

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

iwasrobbed · 5 days ago

FYI Tibo finally posted about this bug: https://x.com/thsottiaux/status/2091407991736332689?s=20

garpunzel · 4 days ago
FYI Tibo finally posted about this bug: https://x.com/thsottiaux/status/2091407991736332689?s=20

“Dude, we're like on TV right now!”