Default auto-compaction waits until 94.7% of usable context because it uses the raw window
What version of Codex CLI is running?
codex-cli 0.149.0-alpha.4.1
What subscription do you have?
ChatGPT Plus
Which model were you using?
gpt-5.6-sol, reasoning effort xhigh, reasoning summaries auto
What platform is your computer?
Linux 6.12.96+deb13-amd64 x86_64 unknown - Debian 13 (trixie), 64-bit
What terminal emulator and version are you using (if applicable)?
VS Code integrated terminal over SSH. No tmux, screen, or zellij.
Codex doctor report
Not available for the original stock reproduction. I diagnosed the issue retrospectively from its persisted rollout.
I captured a doctor report later from the patched validation build, but it is not representative of the original run. Provider reachability and the Responses WebSocket handshake were both healthy.
What issue are you seeing?
In long, tool-heavy threads I noticed that Codex was waiting much longer than expected before compacting. /status showed a usable context window of 258,400 tokens, but automatic compaction was still using a 244,800-token threshold.
Those numbers come from different bases:
raw context window: 272,000
effective context percentage: 95%
usable context window: 258,400
current default auto-compact limit: 244,800 = 90% of 272,000
90% of the usable window: 232,560 = 90% of 258,400
difference: 12,240 tokens
This means the default does not compact at 90% of usable context. It waits until:
244,800 / 258,400 = 94.7368%
That difference becomes expensive in tool-calling loops because each sampling continuation sends the current normalized history again. The practical relationship is:
input-token throughput ≈ active prompt size × sampling-call density
What I observed
The account usage meter was moving by roughly 17–20 percentage points per hour in a fresh thread. A mature thread temporarily exceeded 40 points per hour. Starting a fresh thread restored the lower rate immediately.
The mature rollout contained:
114,915,129 input tokens
112,230,400 cached input tokens
840 sampling completions
9 compactions
238.635 minutes
97.6637% cache reuse
Cache failure does not fit the evidence. The expensive windows were still overwhelmingly cached; cached input was simply being processed repeatedly as part of very large prompts.
The mature thread made 35 sampling calls with request input at or above 232,560 before stock compaction. Those calls processed 8,376,048 input tokens inside the gap created by the threshold mismatch.
That 8.376M is the amount of high-context traffic observed in the gap. It is not a claim of guaranteed net savings, because earlier compaction also has a request cost.
The worst ten-minute windows show why prompt size matters:
| Window | Sampling calls | Input tokens | Cached input | Mean input/call |
|---|---:|---:|---:|---:|
| Mature-thread peak | 46 | 9,833,093 | 9,747,968 | 213,763 |
| Fresh-parent peak | 58 | 6,702,935 | 6,373,888 | 115,568 |
The mature window processed substantially more input despite making fewer calls because its average prompt was approximately 85% larger.
The fresh figures here are parent-rollout figures and are used only for prompt-size comparison. A child rollout overlapped part of the fresh period, so this is not an account-wide usage A/B.
Patched validation
I tested a small default-off change named effective_auto_compact_window. When enabled, and only for the default Total scope with no explicit auto-compaction limit, it derives the threshold from the effective window:
floor(258,400 × 0.90) = 232,560
Explicit limits and BodyAfterPrefix behavior are left unchanged.
A real tool-heavy validation thread completed two compactions:
| Compact | Request input before compact | Active total at trigger | Local compacted estimate | Next request input |
|---:|---:|---:|---:|---:|
| 1 | 221,835 | 234,449 | 27,100 | 29,714 |
| 2 | 229,791 | 236,600 | 26,011 | 29,151 |
Both compactions happened after crossing the corrected 232,560 limit but before reaching the stock 244,800 limit. Stock Codex would not have compacted at either of those decision points.
The replacement histories contained five retained messages and one opaque compaction item, with no raw tool calls or tool outputs.
The session then completed 63 tool calls across the two post-compaction ten-minute windows with:
- no duplicate call IDs;
- no exact earlier tool invocations replayed;
- no compaction errors;
- no context-window errors.
The prompt reset was visible immediately:
| Compact | Ten minutes before | Ten minutes after |
|---:|---|---|
| 1 | 12 calls, 2.524M input, 210.4k mean/call | 30 calls, 1.871M input, 62.4k mean/call |
| 2 | 28 calls, 5.882M input, 210.1k mean/call | 33 calls, 1.638M input, 49.6k mean/call |
Mean input per call dropped by 70.3% and 76.4% after the two compactions. This demonstrates that the replacement worked; those percentages are not the same as patch-versus-stock net savings.
For the complete validation snapshot:
| Metric | Degraded mature thread | Patched validation | Difference |
|---|---:|---:|---:|
| Input tokens/minute | 481,552 | 413,031 | -14.2% |
| Inferred calls/minute | 3.558 | 3.241 | -8.9% |
| Mean input/call | 136,804 | 128,599 | -6.0% |
| p90 input/call | 219,154 | 210,624 | -3.9% |
| Maximum input/call | 244,406 | 229,791 | -6.0% |
| Worst ten-minute input | 9,833,093 | 8,454,284 | -14.0% |
This comparison is encouraging, but it was not a matched provider-facing A/B. The patched run also had lower call density, so the full 14.2% difference cannot be attributed solely to the threshold change.
Provider rollout-budget units and remote-compaction usage are not fully present in the serialized rollout. Exact account-point savings therefore remain unknown.
What steps can reproduce the bug?
Static reproduction
On the affected tag or current main, compare:
codex-rs/protocol/src/openai_models.rs
ModelInfo::auto_compact_token_limit
codex-rs/core/src/session/turn_context.rs
TurnContext::model_context_window
codex-rs/core/src/session/context_window.rs
context_window_token_status
The calculation reduces to:
let raw_context_window = 272_000_i64;
let effective_context_window = raw_context_window * 95 / 100;
let current_default = raw_context_window * 9 / 10;
let expected_default = effective_context_window * 9 / 10;
assert_eq!(effective_context_window, 258_400);
assert_eq!(current_default, 244_800);
assert_eq!(expected_default, 232_560);
ModelInfo::auto_compact_token_limit derives the default from the resolved raw window. TurnContext::model_context_window applies the effective percentage. context_window_token_status then compares active usage against the raw-derived default while using the effective window as the hard limit.
Runtime reproduction
- Run Codex with
gpt-5.6-soland no explicitmodel_auto_compact_token_limit. - Use a long-running task with repeated tool calls.
- Allow the active context to exceed 232,560 tokens.
- Observe that stock Codex does not compact at 90% of the displayed 258,400-token usable window.
- Continue until active usage approaches the raw-derived 244,800 limit.
- Inspect the rollout's
token_countandcompactedrecords. - Repeat using the reference implementation.
- Observe compaction shortly after active total crosses 232,560, followed by a replacement estimate around 26–27k.
Relevant thread IDs:
degraded stock thread: 01a02779-8318-7010-896d-f0fbc2353efa
patched validation: 01a028f5-15e4-7c52-9afd-8179169637b6
The threads contain private repository work, so I have not attached the raw rollouts publicly. I can provide sanitized records or aggregate tables if needed.
What is the expected behavior?
When the auto-compaction scope is Total and no explicit user or model limit is configured, the default threshold should be derived from the effective usable context window:
floor(effective_context_window × 0.90)
For this model, that is:
floor(258,400 × 0.90) = 232,560
Explicit user/model limits should remain authoritative, and BodyAfterPrefix behavior should remain unchanged.
The regular context-window check and the model-downshift pre-compaction check should use the same resolved threshold.
Additional information
The source-level behavior is implemented across:
ModelInfo::auto_compact_token_limitTurnContext::model_context_windowcontext_window_token_status- the sampling continuation loop in
session/turn.rs
A current-main reference implementation is available here:
https://github.com/amikad09/codex/commit/61fd13e84f42e097d3c0e106142f54ebb4ceb276
The change is behind a default-off feature flag and only affects the default Total-scope calculation. It does not override explicit limits or change BodyAfterPrefix.
Validation completed successfully:
- all 38
codex-featurestests; - the targeted core unit test;
- flag-on and flag-off integration tests;
- scoped
codex-coreClippy/fix; - Rust formatting and whitespace checks.
No upstream pull request was opened.
The corrected threshold and prompt reset are demonstrated, but exact ChatGPT account savings still need a matched stock-versus-patched benchmark. Earlier compaction may happen slightly more frequently, and the two observed remote compactions took approximately 85 and 104 seconds.
Related reports:
- #34971 — repeated processing of large cached context and excessive usage
- #35669 — compaction/headroom problems that expose the 244,800 and 258,400 limits
- #27545 — post-sampling compaction and inflated token usage
1 Comment
Potential duplicates detected. Please review them and close your issue if it is a duplicate.
Powered by Codex Action