Auto-compaction uses stale token usage after tool output, causing unrecoverable context overflow

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

Summary

In a long-running app-server turn, a large tool result can push the next sampling request beyond the model context window without triggering auto-compaction. The compaction decision uses the last server-reported token usage, which does not include the newly appended tool output. Once the request fails, small follow-up turns can remain unrecoverable because pre-turn compaction also does not account for incoming context updates and user input.

Observed behavior

From a production app-server rollout (identifiers and prompt contents omitted):

  • Effective model context window: 258,400 tokens
  • Last server-reported input usage: 242,701 tokens
  • The next function_call_output payload was 24,143 JSON bytes
  • No context_compacted event occurred
  • The next sampling request failed with an HTTP 400
  • A tiny follow-up prompt on the resumed thread failed immediately with the same error
  • Changing the selected model on resume did not recover the thread

The rollout contained 149 successful MCP tool calls and 164 token-usage updates before the failure, so this was not a model availability or authentication problem.

Suspected cause

Current main computes estimated_token_count after sampling, but compaction is gated only by token_status.token_limit_reached, which is derived from server-reported usage:

https://github.com/openai/codex/blob/bc8222b8d9e44377a3d7c7b7970e32e7c29ec34f/codex-rs/core/src/session/turn.rs#L315-L350

The estimate is currently only included in tracing. It is not used to decide whether the newly appended tool result makes another sampling request unsafe.

Pre-turn compaction has the same blind spot for newly incoming items; the source already calls this out:

https://github.com/openai/codex/blob/bc8222b8d9e44377a3d7c7b7970e32e7c29ec34f/codex-rs/core/src/session/turn.rs#L153-L157

The actual pre-turn gate checks only the persisted token status before recording incoming context/user input:

https://github.com/openai/codex/blob/bc8222b8d9e44377a3d7c7b7970e32e7c29ec34f/codex-rs/core/src/session/turn.rs#L800-L822

Expected behavior

  • Before a follow-up sampling request, compaction should consider the estimated model-visible context after newly appended tool outputs.
  • Before the first sampling request of a resumed turn, compaction should include pending context reinjection/diffs and the new user input.
  • A thread near the context boundary should compact and remain resumable instead of becoming permanently stuck on 400 responses.

Suggested regression test

Use a deliberately small context/auto-compaction limit:

  1. Return server token usage just below the compaction threshold.
  2. Append a tool result large enough that the estimated next request exceeds the threshold/window.
  3. Assert that compaction runs before the next sampling request.
  4. Persist/resume the same near-limit thread with a small user input and assert that pre-turn compaction runs before sampling.

Possible fix direction

Use the estimated post-append token count in the mid-turn compaction decision (conservatively taking the maximum of server usage and the local estimate), and estimate pending incoming items in run_pre_sampling_compact before deciding whether to compact.

View original on GitHub ↗

4 Comments

github-actions[bot] contributor · 1 month ago

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

  • #31535

Powered by Codex Action

Osoul1214 · 1 month ago

Adding user-facing Codex Desktop evidence from duplicate issue #34017:

  • After the conversation reported Codex ran out of room in the model's context window, short follow-up messages continued to fail with the same error.
  • Entering /compact also failed to recover the conversation; the thread remained unusable.
  • One occurrence included stream disconnected before completion: Incomplete response returned, reason: max_output_tokens before the context-overflow message.
  • The affected surface was Codex Desktop / VS Code, observed July 15-18, 2026.

This confirms the same recovery failure described here: stale or incomplete compaction accounting can leave a near-limit conversation permanently unrecoverable. Issue #34017 is being closed as a duplicate; its additional value is the Desktop /compact reproduction and screenshot evidence.

starriet9 · 15 days ago

I found and validated an additional accounting edge case while implementing a candidate fix for this issue based on rust-v0.147.0.

A successful provider response can omit numeric token usage. A fix that keeps the last numeric usage value but moves its local-history boundary to the latest model-generated item is still unsafe: items after the last usage-bearing response and before the usage-less response disappear from the estimated growth, even though the provider baseline never included them.

The root cause is allowing the trustworthy numeric baseline and its corresponding local-history boundary to advance independently. Let S0 be the last trustworthy provider usage total and B0 its corresponding boundary. If a later successful response omits usage, retaining S0 while advancing the boundary causes the items between the two boundaries to disappear from locally estimated growth. For a compaction threshold T, this can skip a required compaction whenever the partial estimate remains below T but the full growth after B0 would cross it. This accounting invariant is independent of the configured context-window size.

The candidate fix treats a trustworthy usage sample and its history boundary as one atomic pair:

  • Only a response carrying a real numeric usage sample may replace both.
  • A successful response with missing usage may update newer model-context metadata, but it must preserve the previous numeric baseline and boundary.
  • Resume and fork reconstruct the pair in rollout order.
  • Compaction, history replacement, and rollback invalidate a boundary attached to the replaced history.
  • The next-request decision is the last trustworthy provider baseline plus the locally estimated model-visible growth after that exact boundary.

The regression fixture used a numeric 4,800-token baseline, a usage-less successful response, and enough locally appended data to cross a synthetic 5,000-token limit only when the baseline is retained. It also covered a newer numeric provider sample moving the boundary, legacy zero-valued usage placeholders on resume, fork reconstruction, rollback/history-replacement invalidation, and one-compaction loop prevention. A packaged loopback test made exactly four requests: two ordinary samples, the required compaction request, and the post-summary sample.

Current main still bases the decision on get_total_token_usage() while the fresh post-sampling estimate is trace-only:

This does not change the original report's fix direction; it adds a persistence requirement that prevents a partial fix from losing growth whenever a successful completion omits usage.

shleder · 14 days ago

Your boundary is unusually concrete: a 24 KB tool result landed after a 242,701-token server baseline, no compaction occurred, and even tiny prompts on the resumed thread kept failing with HTTP 400.

Codex Rescue cannot fix Codex’s token accounting or perform speculative general compaction. I’m trying to validate whether the surviving rollout is diagnosed conservatively and whether a safe repository-state handoff is possible when the original thread cannot continue. It leaves the rollout untouched, never replays unknown calls, and verification may return REVIEW_REQUIRED.

If the affected session still exists, could you run:

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

Please share sanitized output only—no rollout, database, tool contents, prompts, paths, or secrets.

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