Skip to content

Automatic compaction double-counts prior reasoning against reported token usage and triggers unnecessary compaction #49026

Open

Description

@TechnikEmpire

What version of Codex CLI is running?

Maintained Codex fork based on upstream commit 12de0e3

What subscription do you have?

ChatGPT Pro — $200/month

Which model were you using?

GPT-6 Astra Ultra

What platform is your computer?

Windows 10 x64

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

No response

Codex doctor report

What issue are you seeing?

Summary

Codex prematurely triggered automatic compaction because its context estimator added 72,772 estimated tokens for prior encrypted reasoning on top of a completed response's 177,413 reported tokens.

The resulting count was 250,193 tokens, crossing the 244,800 automatic-compaction threshold, even though the last reported usage remained 67,387 tokens below that threshold.

The incorrect threshold decision then caused a real additional remote compaction request consuming:

  • 177,445 input tokens
  • 176,768 cached input tokens (already included in input)
  • 4,091 output tokens
  • 181,536 total reported tokens

There was no manual compaction request.

Exact calculation

Immediately before compaction:

Measurement Tokens
Last response input 176,904
Cached input, included above 173,952
Last response output 509
Reasoning output, included above 361
Last reported total 177,413
Estimated earlier encrypted reasoning 72,772
Newly appended item estimate 8
Count used for automatic compaction 250,193
Automatic-compaction threshold 244,800
Effective context-window limit 258,400

The decision was:

177,413 + 72,772 + 8 = 250,193

250,193 >= 244,800  -> automatic compaction
250,193 < 258,400   -> full context-window limit not reached

Reconstructing the recorded history reproduces the 250,193-token count exactly.

Root cause

The additional 72,772 tokens came from 148 encrypted reasoning items from earlier model responses retained in history.

When server_reasoning_included is false, Codex effectively calculates:

last reported total
+ estimated earlier reasoning
+ newly added items

The affected WebSocket handshake did not contain:

X-Reasoning-Included

so this branch was used.

The defect is that a historical subset is added to an already completed request's reported usage without establishing that the two quantities are disjoint.

The 148 reasoning items were not newly generated reasoning. They matched the archived preceding request input by hash.

Actual remote operation caused by the bug

Operation Input Cached input Output Total
Response before compaction 176,904 173,952 509 177,413
Automatic compaction 177,445 176,768 4,091 181,536
First response afterward 28,684 12,288 80 28,764

The compaction began immediately after the incorrect threshold decision.

This was not a UI-only accounting error. The incorrect context calculation directly caused an additional remote model operation.

Upstream code

The running fork is based on upstream commit:

12de0e395d3313bc564190d983cb4f5acf0be713

These functions were compared directly with that upstream commit and are unchanged:

codex-rs/core/src/context_manager/history.rs

get_non_last_reasoning_items_tokens
get_total_token_usage
is_user_turn_boundary

Relevant upstream code:

core/src/context_manager/history.rs
    prior-reasoning estimate and total-token calculation

core/src/state/session.rs
    server_reasoning_included state and history accounting

core/src/session/context_window.rs
    automatic-compaction threshold comparison

core/src/session/turn.rs
    server flag handling and compaction decision

codex-api/src/sse/responses.rs
    X-Reasoning-Included handling

codex-api/src/endpoint/responses_websocket.rs
    corresponding WebSocket handling

Correct accounting

For a settled request with a complete request-bound estimate, the context baseline should compare complete quantities rather than add historical reasoning to an already completed total:

baseline = max(
    last server-reported input + output,
    estimated complete composed input + actual output
)

next context estimate =
    baseline + genuinely new local-item estimate

For this incident:

archived complete-input estimate: 201,228
developer-message normalization:    -990
inserted text upper bound:          +2,812
                                     -------
complete composed-input bound:     203,050

Therefore:

max(177,413, 203,050 + 509) + 8
= 203,567

203,567 < 244,800

The correct decision does not trigger automatic compaction.

Related open issues

This sits within a much larger existing set of open Codex usage/accounting defects:

Customer impact

I pay $200/month for ChatGPT Pro, and I now maintain both my own Codex fork and an increasingly substantial local instrumentation and patching layer around it.

I find the current situation completely absurd: I am spending a meaningful portion of my paid OpenAI allowance using OpenAI's own models to identify, diagnose, and patch defects in OpenAI's own Codex client that themselves waste that allowance.

This bug is a concrete example. An incorrect client-side accounting decision caused an additional 181,536-token remote operation that should not have occurred at that point.

This is also not an isolated class of problem. The issue tracker already contains a substantial number of open reports involving incorrect accounting, unnecessary context replay, runaway polling, premature compaction, background activity, and subscription usage that does not reconcile with observable work.

A normal client bug wastes time.

A client bug that causes unnecessary inference consumes the product the customer paid for.

At $200/month, I should not have to spend an increasing portion of that same finite allowance maintaining a private patch layer around the official client simply to prevent avoidable usage consumption.

What steps can reproduce the bug?

  1. Use a Codex session containing retained encrypted reasoning items from earlier model responses.

  2. Reach a completed response where the server-reported token total remains below the automatic-compaction threshold.

  3. Have server_reasoning_included == false, as occurs when the response does not contain X-Reasoning-Included.

  4. Append a new history item after that completed response.

  5. Allow the normal Codex history-accounting path to evaluate the automatic-compaction threshold.

In the captured incident:

last reported total:               177,413
estimated prior reasoning:          72,772
new-item estimate:                       8
                                     -------
calculated context count:          250,193

automatic-compaction threshold:    244,800
context-window limit:              258,400

Codex therefore triggers automatic compaction even though the last reported total remains 67,387 tokens below the threshold.

The 72,772-token component is produced from the 148 retained encrypted reasoning items selected by the history estimator.

Reconstructing the recorded history reproduces the final 250,193-token count exactly.

The resulting automatic compaction produces an additional remote request:

input_tokens:          177,445
cached_input_tokens:   176,768
output_tokens:           4,091
total_tokens:          181,536

Relevant unchanged upstream functions:

codex-rs/core/src/context_manager/history.rs

get_non_last_reasoning_items_tokens
get_total_token_usage
is_user_turn_boundary

Upstream base:

12de0e395d3313bc564190d983cb4f5acf0be713

What is the expected behavior?

No response

Additional information

No response

Activity

  1. added
    CLIIssues related to the Codex CLI
    contextIssues related to context management (including compaction)
    rate-limitsIssues related to rate limits, quotas, and token usage reporting
    on Sep 28, 2026
  2. Turingfly commented on Sep 29, 2026

    @Turingfly

    Independent corroboration on official Codex CLI 0.158.0/macOS, GPT-6 Astra, reasoning.context=all_turns, captured through claude-tap HTTP/SSE.

    The server attributes 13,454 input tokens to 70 historical encrypted reasoning items before the latest instruction boundary (including AgentMessage boundaries). They have empty summaries and no plaintext content. Summing ALL item input attribution exactly equals the reported 250,478 input tokens. In 56/59 matched items, replayed input equals the original reasoning output plus two tokens, so this is not merely envelope overhead.

    The captured response has no x-reasoning-included header. Native logs exactly match:

    251,211 server total + 23,841 estimated historical reasoning + 653 new tool-result estimate = 275,705 client context count.

    This verifies that already-counted historical reasoning is added again as an estimate. The 0.158.0 history.rs fallback matches the calculation. This request stayed below its compaction threshold: it proves the accounting overlap, not an additional compaction. The capture does not identify which upstream component caused the missing signal.

    Please validate the reasoning-included contract for both SSE and WebSocket responses. Related: #39767; #32483 concerns the separate pre-turn reset path.

  3. coinman-dev commented on Oct 4, 2026

    @coinman-dev

    Same cause here on Codex 0.159.2 with gpt-6.1-sol under WSL2. A small client patch that stops the double count is described in #39767 (comment): with it the same chat went from being compacted at 734,065 tokens to 922,856 with no early compaction, and its regression test fails on unpatched 0.160.0. Patch and notes: https://github.com/coinman-dev/codex-context-accounting-fix

  4. Zhuchen00123 commented on Oct 11, 2026

    @Zhuchen00123

    Synthetic-only corroboration of the native Responses Lite / all-turns accounting overlap. This comment contains no private rollout data, local configuration, account identifiers, or installed-client environment details.

    Minimal protocol observation

    Using gpt-6.1-sol, I generated a fresh encrypted reasoning item with:

    Compute 127 times 193. Check your calculation and reply with the number only.
    

    The generation requested reasoning.effort="high", reasoning.context="all_turns", and include=["reasoning.encrypted_content"].

    I then compared two small HTTP/SSE requests to the official Codex Responses endpoint, with Responses Lite enabled, reasoning.context="all_turns", and low reasoning effort:

    1. Input: a user message Reply OK..
    2. Input: the newly generated encrypted reasoning item, with summary: [] and no plaintext content, followed by the same user message.

    Both requests returned HTTP 200 and response.completed. Neither observed response contained x-reasoning-included.

    Synthetic observation Value
    Baseline input usage 9 tokens
    Input usage with the synthetic old reasoning item 50 tokens
    Input usage attributed specifically to that replayed reasoning item 41 tokens
    Output usage attributed to that replayed item 0 tokens
    Original generation's reasoning output 39 tokens
    Final replay response total usage 55 tokens
    Encrypted item's encoded length 1,892 bytes
    Codex's encrypted-length estimate for that old item 193 tokens

    The replayed item's 41 input tokens exactly explain the input delta (50 - 9). This establishes that the observed route counts historical encrypted reasoning in reported input usage without the compatibility header. The estimate is:

    ceil(max(floor(1892 * 3 / 4) - 650, 0) / 4) = 193
    

    For this composed history, the false-flag fallback would therefore add a 193-token estimate to the already inclusive 55-token reported total. That addition is overlapping accounting; the encrypted-length estimate also differs substantially from the actual item attribution. This arithmetic is a source-based implication, not an assertion that a desktop compaction was triggered by this tiny protocol sample.

    Public upstream source

    The relevant paths remain in upstream main inspected at 8c0f7011674145da7e10627769fb221c74fbf7a0:

    This complements #39767's all-turns observations and #32483's pre-turn ordering issue. The synthetic results do not identify which upstream component omitted the header and should not be extrapolated to every provider.

    Could maintainers confirm the native Responses Lite / all-turns usage contract? If earlier reasoning is included in reported usage, compaction should avoid adding its estimate again. The accounting contract should stay associated with the corresponding successful usage, including across pre-turn checks, failed/cancelled streams, and resume/fork.

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)rate-limitsIssues related to rate limits, quotas, and token usage reporting

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions