claude-code - 💡(How to fix) Fix [BUG] Historical user-message content drift invalidates prompt cache prefix (trailing \n on </system-reminder>) [4 comments, 3 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
anthropics/claude-code#48734Fetched 2026-04-16 06:52:27
View on GitHub
Comments
4
Participants
3
Timeline
9
Reactions
1
Author
Timeline (top)
labeled ×5commented ×4

Fix Action

Fix / Workaround

Workaround we deployed client-side (via fetch interceptor): rstrip trailing whitespace from every text/content field before send. Makes the bug invisible at the cost of normalizing whitespace. But the underlying non-determinism remains and will bite anyone not running an interceptor.

Code Example

msg[749].content[2] (text block):
  healthy turn:  "...[action-tracker.py] Allowed\n</system-reminder>\n"
  miss turn:     "...[action-tracker.py] Allowed\n</system-reminder>"   <-- trailing \n stripped

msg[768].content[2] (text block):
  healthy turn:  "...[action-tracker.py] Allowed\n</system-reminder>\n"
  miss turn:     "...[action-tracker.py] Allowed\n</system-reminder>"
RAW_BUFFERClick to expand / collapse

Preflight Checklist

  • I have searched existing issues and this hasn't been reported yet
  • This is a single bug report
  • I am using the latest version of Claude Code (2.1.109)

What's Wrong?

Between two adjacent turns of the same Claude Code session, CC re-serializes historical user-message content with byte-level instability — specifically, a trailing \n after </system-reminder> is sometimes present and sometimes absent. A single 2-byte delta on any historical message invalidates the entire prompt-cache prefix for that turn, causing cache_read_input_tokens to drop to ~19k (tools block only) while cache_creation_input_tokens spikes to the full conversation size (~500k+ in our session).

This is distinct from #40652 (cch= hash substitution), #43657 (resume block scatter), #44045 (skill_listing scatter), and #48644 (isMeta positional drift) — it's content-level mutation of historical text blocks, not positional drift or a hash substitution.

Evidence (body-dump diff across a known cache miss)

Two consecutive /v1/messages request bodies captured via a fetch interceptor, one from a healthy cache-hit turn and the next from a full-cache-miss turn. 867 of 869 historical messages match byte-for-byte. The only diffs:

msg[749].content[2] (text block):
  healthy turn:  "...[action-tracker.py] Allowed\n</system-reminder>\n"
  miss turn:     "...[action-tracker.py] Allowed\n</system-reminder>"   <-- trailing \n stripped

msg[768].content[2] (text block):
  healthy turn:  "...[action-tracker.py] Allowed\n</system-reminder>\n"
  miss turn:     "...[action-tracker.py] Allowed\n</system-reminder>"

Same historical messages, same logical content, 2 bytes different → full prefix invalidates.

Reproduced 3+ times in the same session with identical pattern on different historical indices. The drift is stochastic per-turn (no correlation with turn density, idle time, or specific tool use patterns). Looks like a non-deterministic code path in CC's transcript-to-messages serializer.

Session details

  • Model: claude-opus-4-6 (1M context)
  • CC version: 2.1.109
  • Platform: Windows 11 (npm-installed @anthropic-ai/claude-code launched via node cli.js)
  • Approximate session size when miss fires: ~770 messages, ~1.7MB JSON request body
  • Per-miss cost: ~500k cache_creation_input_tokens × Opus pricing ≈ $9/event

What Should Happen

Historical messages[i].content[j].text should be byte-stable across turns. Whatever serializer path strips the trailing \n from </system-reminder> blocks on some turns should produce deterministic output.

Workaround we deployed client-side (via fetch interceptor): rstrip trailing whitespace from every text/content field before send. Makes the bug invisible at the cost of normalizing whitespace. But the underlying non-determinism remains and will bite anyone not running an interceptor.

Additional context

Related to the broader cache-invalidation bug family around CC's message serialization:

  • #40652 cch= hash substitution
  • #43657 resume cache invalidation
  • #44045 skill_listing block scatter
  • #48644 hidden isMeta reminder position drift
  • #41930 widespread usage drain (umbrella)

Given Anthropic's recent quota/pricing changes, these cache invalidation bugs have gone from "mildly annoying" to "session-ending expensive" for heavy users. A few bytes of non-determinism in historical content now costs ~$9 per occurrence instead of being invisible.

extent analysis

TL;DR

The most likely fix is to ensure deterministic output from the serializer path that strips the trailing \n from </system-reminder> blocks.

Guidance

  • Identify the specific code path in the Claude Code serializer that is responsible for stripping the trailing \n from </system-reminder> blocks and modify it to produce deterministic output.
  • Verify that the fix works by capturing request bodies across multiple turns and checking for byte-level stability of historical message content.
  • Consider implementing a server-side fix to normalize whitespace in messages[i].content[j].text fields to prevent similar issues in the future.
  • Review related cache-invalidation bugs (e.g., #40652, #43657, #44045, #48644) to ensure that the fix does not introduce new issues.

Example

No code snippet is provided as the issue does not contain sufficient information about the serializer code.

Notes

The fix may require changes to the Claude Code serializer, which could have implications for other parts of the system. It is essential to thoroughly test the fix to ensure that it does not introduce new issues.

Recommendation

Apply a workaround, such as the client-side fix deployed via fetch interceptor, to rstrip trailing whitespace from every text/content field before send, until a server-side fix can be implemented. This will make the bug invisible at the cost of normalizing whitespace, but the underlying non-determinism will remain.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

claude-code - 💡(How to fix) Fix [BUG] Historical user-message content drift invalidates prompt cache prefix (trailing \n on </system-reminder>) [4 comments, 3 participants]