Skip to content

GPT-5.6 all-turns reasoning is double-counted, causing auto-compaction with ~20% context remaining #39767

Open

Description

@telxs

What version of Codex CLI is running?

codex-cli 0.148.0

What subscription do you have?

ChatGPT Pro

Which model were you using?

gpt-5.6-sol

What platform is your computer?

Microsoft Windows NT 10.0.26200.0 x64

What terminal emulator and version are you using (if applicable)?

No response

Codex doctor report

What issue are you seeing?

Summary

In a long-running gpt-5.6-sol rollout whose initial session metadata records Codex CLI 0.144.5, Codex automatically compacted the conversation while approximately 20% of the effective context window was still available. The thread may have subsequently been resumed by newer Codex builds, but the rollout does not record a client version for each resume or turn.

Analysis of the rollout shows that the API-reported token usage already accounted for historical reasoning items. However, Codex behaved as if server_reasoning_included were false and added an estimated size for the same historical reasoning items again.

This caused historical reasoning to be double-counted for the auto-compaction decision.

Across 76 automatic compactions in the analyzed rollout:

  • The current server_reasoning_included = false calculation crossed the auto-compaction limit in 76/76 cases.
  • The server_reasoning_included = true calculation crossed the limit in 0/76 cases.

Analyzed rollout

  • Model: gpt-5.6-sol
  • OS: Windows
  • Session creation version: Codex CLI 0.144.5
    • This is the version recorded in the rollout's initial session_meta.
    • The thread was long-running and may have been resumed by newer Codex builds.
    • The rollout does not record the client version for every resume or turn.
  • Current source inspection: the same accounting path is still present on main at commit 3b45c29062ff0e76e71c91b6753290400e7fa8da (2026-08-19).
  • Effective context window in the analyzed rollout: 272,000 tokens
  • Auto-compaction limit: 244,800 tokens
  • Reasoning context: all_turns
  • Rollout ID: 019f74ae-8c3d-77e0-8725-2b2c99546fba

Therefore, this report should not be interpreted as being limited to CLI 0.144.5. That version is directly confirmed by rollout metadata; newer builds remain susceptible at the source-code level when the server already includes historical reasoning but the reasoning-included signal is absent or not propagated.

GPT-5.6 defaults to persisted all_turns reasoning according to the official model guidance:

https://developers.openai.com/api/docs/guides/latest-model

Actual behavior

Codex computes the auto-compaction token usage through:

last_tokens
    .saturating_add(self.get_non_last_reasoning_items_tokens())
    .saturating_add(items_after_last_model_generated_tokens)

when server_reasoning_included is false.

For this rollout, the historical reasoning represented by get_non_last_reasoning_items_tokens() was already reflected in the API-provided token usage, so this estimate counted the same reasoning a second time.

As a result, automatic compaction was triggered significantly before the real request context reached the configured limit.

Evidence

1. Reconstructing server input-token accounting

I reconstructed the model-visible request history across 1,755 consecutive cross-turn transitions and compared the next response's server-reported input_tokens against two predictions:

Reconstruction Median absolute error
Historical reasoning retained/accounted 25 tokens
All historical reasoning excluded 13,483 tokens

The server-reported input_tokens closely match the reconstruction that includes historical reasoning.

The alternative hypothesis—that the server excludes previous reasoning—has a median error larger by more than 13,000 tokens.

This indicates that historical reasoning was already included in the service token accounting for this rollout.

2. Replaying every automatic compaction

I replayed all 76 automatic compaction decisions using both branches of get_total_token_usage().

Calculation Compactions crossing 244,800
server_reasoning_included = false 76/76
server_reasoning_included = true 0/76

There were no preceding new_context_window tool calls or other manual compaction triggers. These were automatic token-limit compactions.

3. Concrete example

One compaction was triggered with:

last server-reported total       = 209,336
historical reasoning estimate    =  33,371
items after last model response  =   4,726
auto-compaction limit            = 244,800

Current calculation:

209,336 + 33,371 + 4,726 = 247,433
247,433 > 244,800

Therefore Codex compacts.

Calculation without double-counting historical reasoning:

209,336 + 4,726 = 214,062
214,062 < 244,800

With a 272,000-token effective context window, this leaves approximately:

(272,000 - 214,062) / 272,000 = 21.3%

This matches the observed UI state: auto-compaction occurs with roughly 20% context remaining.

Relevant code path

At the start of every regular turn, Codex resets the flag:

sess.set_server_reasoning_included(/*included*/ false).await;

The flag becomes true only when a ServerReasoningIncluded(true) event is received.

For SSE, this event is emitted only when the response contains the x-reasoning-included header.

For WebSocket transport, the same information is derived from the WebSocket handshake header and emitted for each request made over that connection.

The token calculation then uses the flag here:

pub(crate) fn get_total_token_usage(
    &self,
    server_reasoning_included: bool,
) -> i64

When the flag remains false, historical reasoning is estimated and added again.

Relevant files:

  • codex-rs/core/src/tasks/regular.rs
  • codex-rs/core/src/context_manager/history.rs
  • codex-rs/codex-api/src/sse/responses.rs
  • codex-rs/codex-api/src/endpoint/responses_websocket.rs

Likely root cause

For this session, the client behaved as if server_reasoning_included were false even though the returned token usage already accounted for historical reasoning.

This suggests one of the following:

  1. The backend route did not emit x-reasoning-included even though its usage accounting included historical reasoning.
  2. The header was emitted upstream but was not propagated through the relevant response or WebSocket path.
  3. The server/client contract for GPT-5.6 all_turns token accounting is inconsistent across routes.

I do not have a raw packet capture of the response headers, so the evidence establishes that the client remained on the false accounting branch, but does not distinguish which layer failed to propagate the signal.

What steps can reproduce the bug?

  1. Start or resume a long-running Codex CLI thread using gpt-5.6-sol with persisted reasoning.context = all_turns.
  2. Continue the thread across many model sampling turns so that multiple historical encrypted reasoning items accumulate.
  3. Continue until the context indicator shows approximately 20% remaining.
  4. Submit another normal user turn.
  5. Observe that automatic compaction can run even though the server-reported usage plus the newly appended local items remains below the configured auto-compaction limit.
  6. Inspect the rollout's token_count, response_item.reasoning, and compacted records and replay get_total_token_usage() with server_reasoning_included set to both false and true.

This appears to depend on whether the reasoning-included signal is propagated by the selected backend and transport route. The analyzed rollout provides 76 occurrences of the behavior; the numerical replay is included in the issue description.

What is the expected behavior?

If the server-provided token usage already accounts for historical reasoning, Codex should not add get_non_last_reasoning_items_tokens() again.

The auto-compaction decision should use:

server-reported token usage
+ items added locally after the last model response

without re-estimating reasoning that the server has already counted. Automatic compaction should not run while the resulting usage remains below the configured threshold.

Additional information

No response

Activity

  1. added
    CLIIssues related to the Codex CLI
    contextIssues related to context management (including compaction)
    windows-osIssues related to Codex on Windows systems
    on Aug 20, 2026
  2. dajiaohuang commented on Aug 20, 2026

    @dajiaohuang

    Public source supports a deterministic client-side cause in addition to transport propagation, and this overlaps #32483. On current main (5bcd7b0), RegularTask clears the flag before run_turn performs pre-sampling compaction. That check therefore combines the previous response token usage with a freshly forced false value before any new SSE response or WebSocket request can restore the signal. Header loss can still explain other false branches, but it is not required to explain pre-turn compaction. A regression should assert timing, not only the final compact-request count: consecutive header-bearing responses must not compact before the next request, and the header-clears case should compact only after a headerless response has completed.

  3. telxs commented on Aug 21, 2026

    @telxs
    Author

    @dajiaohuang Thanks — I re-ran the rollout analysis with an independent event-order state machine.

    This report overlaps #32483, but it is not fully covered by it:

    • 14/76 compactions occurred before the first normal sampling response of the turn.
    • 62/76 occurred after one or more current-turn sampling responses.

    All 14 pre-sampling cases continued into normal sampling in the same task, with no model or comp-hash change, so they are
    genuine pre-turn token-limit compactions. However, the historical rollout does not retain the previous response's raw X- Reasoning-Included header. Those 14 cases are therefore compatible with #32483, but I cannot prove whether the previous
    signal was cleared by the startup ordering or was never propagated.

    The other 62 cases are outside the scope of the #32483 fix. They occurred after a current-turn response had completed, when
    ServerReasoningIncluded(true), if emitted, would already have been processed before the post-sampling token check. Moving
    the turn-start reset would not affect those cases.

    Across all 76 compactions, the false branch crosses the 244,800-token limit in 76/76 cases, while the true branch crosses it
    in 0/76 cases.

  4. dajiaohuang commented on Aug 22, 2026

    @dajiaohuang

    Thanks — this split materially narrows the diagnosis. I agree that my earlier public-source finding explains only the 14 pre-sampling cases: those remain consistent with #32483's reset ordering, but the historical rollout cannot prove whether the preceding response ever supplied the signal. It cannot explain the 62 post-sampling cases.

    Current main at 343074d4207d572809bd8cea15f4be1d09d98e0b still resets server_reasoning_included before run_turn, and neither #32483 nor this issue has a linked PR. For the 62 post-sampling cases, the useful discriminator is response-scoped evidence tying each reported token-usage record to the ServerReasoningIncluded state that qualified it. The current session-level boolean alone cannot distinguish a missing transport signal from a server/accounting-contract mismatch.

    A focused regression should therefore cover a header-bearing current-turn response followed by the post-sampling token check, plus the intended semantics after an explicitly headerless response. Without the historical response headers or equivalent transport telemetry, I would not infer which route caused those 62 cases.

    For account context, I’m using the Pro 20x plan.

  5. coinman-dev commented on Oct 4, 2026

    @coinman-dev

    I hit the same thing on Codex 0.159.2 with gpt-6.1-sol (ChatGPT sign-in, Zed → codex-acp → app-server, Windows and Ubuntu/WSL2) and ended up with a small local patch that removes the double count. External PRs aren't accepted, so I'm leaving it here for reference.

    What I measured. reasoning.context = all_turns, no x-reasoning-included on the responses. At one compaction the server had reported 737,658 tokens; the client added 313,149 as an estimate of earlier encrypted reasoning plus 946 for new tool output, and compacted at 1,051,753.

    What the patch changes

    • core/src/tasks/regular.rs: no longer clears server_reasoning_included before pre-turn compaction has looked at the previous response's usage (the ordering described in Pre-turn compaction clears reasoning-included state before accounting #32483). For Responses Lite models it sets the flag to true.
    • core/src/session/turn.rs: sets the flag for each sampling request from model_info.use_responses_lite, just before the stream starts, so the post-sampling check after a headerless response takes the right branch too.
    • core/tests/suite/compact.rs: a regression test, all_turns_reasoning_without_header_does_not_compact_twice, for both limit scopes, live and after resume.

    Result. The chat that had been compacted at 734,065 tokens later ran to 922,856 server-reported input tokens with no early compaction. The 7 related integration checks pass on 0.159.2 and on 0.160.0; on unpatched 0.160.0 sources the 4 new cases fail with an extra compaction request.

    Patch, build and install notes: https://github.com/coinman-dev/codex-context-accounting-fix

    This is an unofficial local patch. It assumes that for Responses Lite models the reported usage always includes earlier reasoning, which matches the measurements in this issue and in #49026, but only OpenAI can confirm that contract.

  6. dajiaohuang commented on Oct 7, 2026

    @dajiaohuang

    Thanks for sharing the local patch and measurements, @coinman-dev. The two-part fix (reordering the flag reset in 'regular.rs' so pre-turn compaction sees the previous response's state, and initializing the flag from 'model_info.use_responses_lite' before each stream in 'turn.rs') directly addresses both halves of the problem I outlined earlier: the pre-sampling reset bug from #32483 and the post-sampling case where headerless Responses Lite models were being double-counted.

    Your measurement (compaction at 737k reported tokens vs 1.05M client-estimated tokens, and the patched run reaching 922k reported tokens without early compaction) is consistent with the 20% remaining context window reported in this issue. The assumption that Responses Lite models always include prior reasoning in their reported usage matches the telemetry here and in #49026, though as you note only OpenAI can confirm that contract. The regression test covering both live and resumed sessions is also exactly what I suggested earlier was needed. Thanks for publishing the patch as a reference.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    CLIIssues related to the Codex CLIbugSomething isn't workingcontextIssues related to context management (including compaction)windows-osIssues related to Codex on Windows systems

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions