[BUG] totalTokensReminder causes repeatable prompt-cache floor in tool loops; off restores incremental hits

Status Open
Reported on v2.1.246
Maintainer reply None cached
Activity 2 comments · opened Aug 27, 2026

Preflight Checklist

  • [x] I searched existing issues. #88211 covers the meaning of the padded countdown, but not prompt-cache invalidation.
  • [x] This is a single bug report.
  • [x] I reproduced on the latest Claude Code version available to me.

What's Wrong?

With the default totalTokensReminder behavior (padded-countdown), a long tool-use loop repeatedly fell back to the same prompt-cache floor even though the conversation was growing append-only in the local transcript.

In one continuous Claude Desktop session, ten consecutive requests reported cache_read=8192 while uncached input grew from 60,327 to 77,252 tokens:

| Request | input | cache_read |
|---:|---:|---:|
| 1 | 60,327 | 8,192 |
| 2 | 63,476 | 8,192 |
| 3 | 66,656 | 8,192 |
| 4 | 68,497 | 8,192 |
| 5 | 70,562 | 8,192 |
| 6 | 74,477 | 8,192 |
| 7 | 75,292 | 8,192 |
| 8 | 76,161 | 8,192 |
| 9 | 76,735 | 8,192 |
| 10 | 77,252 | 8,192 |

The local transcript contained a changing total_tokens_reminder after tool results during this window, for example:

<total_tokens>14931235 tokens left</total_tokens>
<total_tokens>14926828 tokens left</total_tokens>
<total_tokens>14923572 tokens left</total_tokens>

I then changed exactly one user setting:

{
  "totalTokensReminder": "off"
}

The setting hot-reloaded into the same real Claude Code session. I did not change the Claude Code version, Desktop version, gateway, selected model, tools, or conversation. After the change:

  • total_tokens_reminder attachments in the observed window: 10 before, 0 after.
  • The first request was expectedly cold because removing the reminder changed the system prompt: input=89,880, cache_read=1,024.
  • The next request immediately recovered to input=3,438, cache_read=90,112.
  • Cache reads then grew incrementally to 120,832 while uncached input stayed mostly in the hundreds or low thousands.

Selected post-change requests:

| Request | input | cache_read |
|---:|---:|---:|
| cold after setting change | 89,880 | 1,024 |
| next | 3,438 | 90,112 |
| next | 5,358 | 93,184 |
| next | 613 | 98,304 |
| next | 1,445 | 100,352 |
| next | 1,434 | 103,424 |
| next | 870 | 107,520 |
| next | 329 | 112,640 |
| next | 3,379 | 115,712 |
| next | 2,477 | 118,784 |
| final observed | 1,547 | 120,832 |

hook_success events continued to occur after the setting was disabled, while the cache prefix kept growing. No task_reminder occurred in the post-change window.

This same-session A/B result suggests that the dynamic totalTokensReminder path may be changing the effective cacheable prefix during tool loops. One possible client-side mechanism is the message normalization / reminder-to-tool_result folding family described in #49585 and the historical message drift in #48734.

I do not have the final raw request bodies because my tracing layer intentionally logs hashes and usage but omits message content, so I cannot yet name the exact differing byte or content block.

Important Provider Scope

This was observed through an Anthropic Messages-compatible LLM gateway. The selected Claude Code model alias was claude-fable-5, but the gateway routed to a non-Anthropic upstream model. The gateway, upstream model, and routing stayed unchanged throughout the same-session A/B test.

I have not reproduced this against api.anthropic.com, and I understand that non-Anthropic upstream model behavior may be outside Anthropic's support scope.

I am reporting this because the only changed variable was the Claude Code client setting, and the result matches previously reported client-side reminder re-serialization/cache-floor signatures. It would be useful for the maintainers to verify the outgoing request-body invariant using a first-party endpoint.

What Should Happen?

Changing reminder values should not mutate or re-serialize an already cached historical message prefix. A reminder can be appended at the current tail, or historical serialized messages should remain byte-stable between adjacent tool-loop requests.

With the default setting, consecutive requests should continue reading the previous request's stable message prefix instead of repeatedly falling back to the system/tools cache floor.

Steps to Reproduce

  1. Use default settings with totalTokensReminder unset (padded-countdown).
  2. Start a long-context session and run a task that produces many sequential tool calls.
  3. Confirm that the transcript receives changing attachment.type = "total_tokens_reminder" entries after tool results.
  4. Record per-request input_tokens and cache_read_input_tokens.
  5. If cache reads repeatedly collapse to a fixed system/tools floor, set "totalTokensReminder": "off".
  6. Continue the same session with the same model and tools.
  7. Confirm that new total_tokens_reminder attachments stop and compare whether cache reads resume incremental growth.

For a definitive first-party reproduction, capture two adjacent outgoing /v1/messages bodies and compare the ordered messages content blocks and cache_control placement before and after each tool result.

Environment

  • Claude Code: 2.1.246
  • Claude Desktop: 1.37937.3
  • Entry point: Claude Desktop third-party inference / gateway configuration
  • Operating system: macOS
  • Shell: zsh
  • Context mode: 1M variant enabled
  • Prompt cache controls observed: 3 on the main streaming requests
  • Last locally observed version without total_tokens_reminder: 2.1.229
  • First locally observed version with it: 2.1.234 (this may also involve rollout/feature-gate timing, so I am not asserting a hard introduction boundary)

Related Issues

  • #88211 — exact totalTokensReminder / 15M padded-countdown behavior, but focused on misleading context headroom.
  • #49585 — dynamic system reminders folded into historical tool_result.content, breaking the prompt cache.
  • #48734 — byte drift in historical system-reminder content invalidating the prompt prefix.
  • #63930 — repeated collapse to a system/tools-only cache floor after tool-heavy turns.

I can provide a redacted per-request timeline and message-structure metadata if useful, but I cannot publish the private conversation or tool-result contents.

View original on GitHub ↗

This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗