[BUG] max_tokens recovery mutates prompt history, invalidates cache, and drops the interrupted thinking output

Status Open
Reported on v2.1.220
Maintainer reply None cached
Activity 1 comment · opened Aug 12, 2026

Preflight Checklist

  • [x] I searched existing issues and found no report covering these specific request-payload and cache transitions.
  • [x] This is a single bug report: all three symptoms occur in Claude Code's max_tokens recovery path.
  • [x] Reproduced on Claude Code 2.1.220, the latest stable version at the time of reproduction.

What's Wrong?

Affected version: 2.1.220 (Claude Code), the latest stable version at the time of reproduction.

In two independent sessions, a response reached stop_reason=max_tokens at the 64K output limit. Claude Code then injected this hidden recovery message:

Output token limit hit. Resume directly — no apology, no recap of what you were doing. Pick up mid-thought if that is where the cut happened. Break remaining work into smaller pieces.

The recovery path produced three related problems.

1. Inconsistent continuation request assembly

When max_tokens occurred on the initial request, the continuation rewrote the original two-message prefix:

-[user: U]
-[system: S]
+[user: U + <system-reminder>S</system-reminder> + recovery]

The merged form remained throughout that top-level query. When the query ended and a notification triggered the next top-level user turn, the prefix returned to its original structure:

-[user: U + <system-reminder>S</system-reminder> + recovery]
+[user: U]
+[system: S]
 ...
+[assistant: previous task's final response]
+[user: notification]

At this transition, the prompt-cache hit rate dropped from 99.52% to 15.94%.

In the other session, where max_tokens occurred after conversation history already existed, Claude Code preserved all prior messages and only appended recovery:

 ...
 [user: tool_result]
+[user: recovery]

That continuation correctly preserved the existing prefix and achieved a 99.85% cache hit rate.

2. Recovery is later deleted from existing history

Recovery remained in the prompt across many tool-use requests in the same top-level query. After end_turn, the next user query removed it from the earlier history while normally appending the completed response and new user message at the tail:

 [user: U]
 [system: S]
 ...
-[user: recovery]
 [assistant: responses generated after recovery]
 ...
+[assistant: previous task's final response]
+[user: new query]

Apart from the normal tail additions, recovery was the only historical message removed. The cache transition was:

| State | Cache read | Cache creation | Cache hit |
|---|---:|---:|---:|
| Recovery still present | 244,344 | 532 | 99.78% |
| New user query removes recovery | 29,693 | 216,923 | 12.04% |
| Next request reuses the rebuilt prefix | 246,616 | 1,804 | 99.27% |

Deleting a message that already participated in many generations retroactively changes the prompt prefix and forces a large one-time cache rebuild.

3. The interrupted 64K thinking output is not continued

Both max_tokens responses consisted almost entirely of thinking, but the continuation input grew by only a few dozen tokens:

| Occurrence | Output / Thinking | max_tokens request input | Continuation input | Increase |
|---|---:|---:|---:|---:|
| Mid-conversation | 64,000 / 64,000 | 43,423 | 43,486 | 63 |
| Initial request | 64,000 / 63,999 | 33,402 | 33,479 | 77 |

Input is calculated as cache_read_input_tokens + cache_creation_input_tokens + input_tokens.

The continuation contained no assistant message corresponding to the truncated response, no thinking block or signature from that response, and the signature never appeared in later requests. The model is therefore asked to “Pick up mid-thought” without receiving the interrupted thought in its subsequent prompt history.

What Should Happen?

  1. Continuation should append recovery without rewriting the existing message prefix.
  2. Once recovery has participated in subsequent requests, it should not be removed from existing history when a new user query arrives. If it must be scoped to one top-level query, that scope should not require mutating the prior prefix.
  3. The continuation should include the interrupted assistant output or equivalent resumable state. If that state cannot be retained, the recovery instruction should not tell the model to continue mid-thought.

Error Messages/Logs

There is no user-visible error. The issue is observable in outbound Messages API payloads and response usage fields.

stop_reason=max_tokens
output_tokens=64000
thinking_tokens=64000 (63999 in the other occurrence)

cache hit after deleting recovery:
99.78% -> 12.04%

Steps to Reproduce

  1. Run Claude Code with extended thinking and capture its outbound Messages API request bodies and response usage.
  2. Use a task that causes an Opus response to reach the 64K output limit and return stop_reason=max_tokens.
  3. Inspect the immediate continuation request:
  • If the limit is reached on the initial [user][system] request, compare the first messages before and after recovery.
  • If the limit is reached after conversation history exists, verify that recovery is appended as a hidden user message.
  1. Allow the recovered task to continue through several tool-use turns and return end_turn.
  2. Send another top-level user query and compare the message arrays. Recovery is removed from the earlier history.
  3. Compare cache_read_input_tokens and cache_creation_input_tokens across the boundary.
  4. Search the continuation and later requests for the max_tokens response's assistant content and thinking signature; neither is present.

Claude Model

Opus (claude-opus-4-8)

Is this a regression?

I don't know.

Last Working Version

Unknown.

Claude Code Version

2.1.220 (Claude Code) — the latest stable version at the time of reproduction.

Platform

Other — Anthropic-compatible endpoint. The reported behavior is present in Claude Code's outbound request payload before provider processing.

Operating System

Other Linux

Terminal/Shell

Non-interactive/CI environment

Additional Information

  • Related: #64589 documents the hidden output-token recovery loop, but not the concrete message rewrites/removal and cache transitions reported here.
  • Related: #74427 reports correctness failures at the output-token-limit resume boundary, but focuses on fabricated tool results rather than prompt-history and cache behavior.
  • Sanitized request-payload diffs and usage data can be provided if needed.

View original on GitHub ↗

This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗