Skip to content

Session resume (--continue) invalidates entire prompt cache, causes massive rate limit consumption #42338

Closed

Description

@taekim34

Summary

Resuming a session with --continue or /resume completely invalidates the prompt cache, even when re-entering within seconds. On a long session with a large prompt, this forces a full cache_creation of 400-500k tokens each time — silently burning through rate limits.

What happened

On a 1M context Opus 4.6 session with ~500k prompt, I exited and re-entered with --continue a few times. Each time, cache_read dropped to 0 and the entire prompt was re-cached from scratch — even though the re-entry happened within 2-3 seconds (well within 5-minute cache TTL).

Additionally, the prompt size barely changed after re-entry, meaning whatever restructuring happened during reload provided no benefit — it just destroyed the cache.

All resume methods affected (controlled tests)

Tested with the same session, varying only the resume method and flags:

Test 1: --continue --dangerously-skip-permissions (12s gap)
  Before: cr: 172,301  cc: 1,344
  After:  cr:       0  cc: 174,210  ← full re-cache

Test 2: --continue only (20s gap)
  Before: cr: 176,193  cc:     0
  After:  cr:       0  cc: 176,730  ← full re-cache

Test 3: /resume (interactive resume, ~2.5h gap)
  Before: cr: 160,149  cc:   314
  After:  cr:       0  cc: 156,682  ← full re-cache

All three: cache_read: 0, full re-cache. The --dangerously-skip-permissions flag is irrelevant — it's the resume/continue mechanism itself.

Raw Data (deduplicated by msg_id, from JSONL session log)

Spike 1: session resume after ~4h sleep (expected)

04:18:37  cr: 389,000  cc:     210  out:   640  stop:end_turn     ← last turn before sleep
         ... ~4 hours gap (cache TTL expired — this one is expected)
08:08:09  cr:       0  cc:       0  out:     0  stop:stop_sequence ← session resume
08:09:47  cr:       0  cc: 367,175  out:   822  stop:tool_use      ← 367k full re-cache (expected)

Spike 2: /exit → --continue, 2 seconds apart

09:04:15  cr: 499,517  cc:     486  out:   521  stop:end_turn      ← last turn before /exit
         ... /exit at 09:04:29, --continue at 09:04:31 (2 SECONDS)
09:04:31  cr:       0  cc:       0  out:     0  stop:stop_sequence ← session reload
09:05:13  cr:  26,691  cc: 470,381  out: 1,352  stop:tool_use      ← 470k re-cache! (WHY?)

Spike 3: /exit → --continue, 3 seconds apart

09:40:19  cr: 561,082  cc:     987  out:   289  stop:end_turn      ← last turn before /exit
         ... /exit at 09:40:21, --continue at 09:40:24 (3 SECONDS)
09:40:24  cr:       0  cc:       0  out:     0  stop:stop_sequence ← session reload
09:42:59  cr:  26,691  cc: 511,847  out: 1,649  stop:tool_use      ← 512k re-cache! (WHY?)

Messages in JSONL right before each spike

Spike 2 context:
  /exit command logged
  file-history-snapshot
  custom-title
  agent-name
  file-history-snapshot
  → stop_sequence "No response requested."
  → cr:0 cc:470,381

Spike 3 context:
  /exit command logged
  file-history-snapshot
  custom-title
  agent-name
  file-history-snapshot
  → stop_sequence "No response requested."
  → cr:0 cc:511,847

Before/after comparison

Before /exit  → After --continue (first turn)
prompt: 500k  → 497k (-0.6%)    cache_create: 470,381 (entire prompt re-cached)
prompt: 561k  → 538k (-4%)      cache_create: 511,847 (entire prompt re-cached)

Impact

In a 2.6-hour morning window:

  • 3 session resumes consumed ~1.43M cache_creation tokens
  • Actual useful output during same period: ~155k tokens
  • cache_creation was 9x the actual output
  • Hourly rate limit cost: 920k/hr vs normal 412k/hr (2.2x increase)

Root Cause

Per @ArkNill's analysis: the deferred_tools_delta feature introduced in v2.1.69 reorders tool results when reconstructing the conversation for resume, which breaks the cache prefix and forces a full re-cache. See #34629 and https://github.com/ArkNill/claude-code-cache-analysis.

Environment

  • Model: claude-opus-4-6 (1M context)
  • Platform: macOS
  • Multiple plugins/skills (~40k system prompt tokens)
  • Session duration: ~34 hours (long-running)

Related

Activity

  1. changed the title [-]Autocompact triggers at 43-56% context usage, invalidates cache, causes massive rate limit consumption[/-] [+]Session resume (--continue) invalidates entire prompt cache, causes massive rate limit consumption[/+] on Apr 2, 2026
  2. ArkNill commented on Apr 2, 2026

    @ArkNill

    Your JSONL analysis confirms exactly what I've been tracking across multiple reports. The root cause is the deferred_tools_delta feature introduced in v2.1.69 — it reorders tool results when reconstructing the conversation for resume, which breaks the cache prefix and forces a full re-cache. This is the same mechanism behind #34629.

    Your Spike 2 and Spike 3 data (2-3 second exit→resume gaps with 470-512K re-cache) are particularly damning evidence, since the 5-minute cache TTL should have trivially covered that interval.

    Compounding factor worth noting

    This interacts badly with thinking signatures (#42260). When resumed, base64-encoded thinking blocks from prior turns get replayed as input tokens. Combined with zero cache hits from the prefix break, a 400+ message session can easily send 500K+ tokens as fully-billed input on a single resumed turn — effectively a token bomb.

    What I'd recommend until this is fixed

    • Avoid --resume / --continue entirely for any non-trivial session. Starting fresh with a well-structured CLAUDE.md to restore context is dramatically cheaper.
    • If you must track session state, export context to a file and feed it into a new session rather than resuming.
    • The cache_read_input_tokens field in your JSONL logs is the key diagnostic — any resume that shows near-zero cache read with high cache creation is hitting this bug.

    I patched v2.1.81 to fix the cache prefix handling and verified cache read ratios returned to 89-99% on fresh sessions. The broader cache analysis (including this resume-specific issue) is documented at https://github.com/ArkNill/claude-code-cache-analysis.

  3. taekim34 commented on Apr 2, 2026

    @taekim34
    Author

    Thanks @ArkNill for the deferred_tools_delta root cause analysis and the link to your cache analysis repo. Very helpful!

    I've updated the issue body with additional test results confirming /resume also triggers the same cache invalidation.

  4. ArkNill commented on Apr 2, 2026

    @ArkNill

    Update (April 2): v2.1.90 fixes the main cache regression but this specific issue (--resume / session replay) remains. Avoid --resume entirely — fresh sessions with a structured CLAUDE.md are the workaround.

    For non-resume usage, v2.1.90 now achieves 95-99% cache read on both npm and standalone. Details: https://github.com/ArkNill/claude-code-cache-analysis

  5. simpolism commented on Apr 2, 2026

    @simpolism

    2.1.90 has this line in the changelog:

    Fixed --resume causing a full prompt-cache miss on the first request for users with deferred tools, MCP servers, or custom agents (regression since v2.1.69)

    So I assume that was another bug? Because I cannot use the harnesses I built around claude -p --resume given the reported bug here. They eat my entire quota within a few turns.

  6. ArkNill commented on Apr 3, 2026

    @ArkNill

    April 3 update: The cache regression (Bugs 1-2) is fixed in v2.1.91. However, systematic proxy testing revealed additional unfixed mechanisms — a 200K tool result budget cap, a client-side false rate limiter (151 synthetic entries found), and silent microcompact clearing (327 events). Anthropic responded on X (Lydia Hallie) acknowledging peak-hour tightening but stating "none were over-charging you" — our measured data shows mechanisms their statement does not cover. Full analysis: claude-code-cache-analysis

  7. ArkNill commented on Apr 3, 2026

    @ArkNill

    Received a patch via email from @simpolism that addresses the remaining --resume cache misses on v2.1.91. Tested it locally through a monitoring proxy — results are solid.

    The patch targets two additional root causes beyond what v2.1.90-91 fixed:

    1. Skill listing attachment — skill_listing (~375 tokens) isn't persisted in session storage. On resume it regenerates at a different position, changing the block structure and breaking the cache prefix.

    2. joinTextAtSeam newline accumulation — text-block merging function appends \n at boundaries during normalization. On replay, these accumulate, mutating the prefix each resume.

    My test results on v2.1.91 (npm, patched, through cc-relay proxy):

    Turn Type Create Read Ratio
    1 Initial 3,963 25,576 86.6%
    3 Resume 1 4,799 29,539 86.0%
    4 Resume 2 89 34,338 99.7%
    5 Resume 3 28 34,427 99.9%
    7 Resume 4 (30s idle) 36 36,964 99.9%

    From resume 2 onward: 28-89 tokens created per turn instead of tens of thousands. Context carries over correctly — the 4th resume accurately summarized the entire session history.

    The patch needed a small adaptation for v2.1.91 (the original regex for joinTextAtSeam didn't match due to a literal newline in the minified template string — used brace-counting to find exact function boundaries instead).

    Original gist: https://gist.github.com/simpolism/302621e661f462f3e78684d96bf307ba

    For anyone using claude -p --resume in harness setups, this is worth looking at. For interactive use where you start fresh sessions, the impact is minimal since v2.1.90-91 already fixed the main cache regression.

  8. simpolism commented on Apr 3, 2026

    @simpolism

    @ArkNill thanks for posting it here -- I think there may still be a cache consistency issue around tool calls or file tracking across resumes. Hopefully can look into it shortly, having trouble reproducing the exact circumstances but got a pretty bad cache hit during a regular conversation on my patched version and will be keeping an eye out.

  9. simpolism commented on Apr 3, 2026

    @simpolism

    okay @ArkNill we found the remaining cache-breaking issues, they involved the TodoWrite and other injections happening before the cache prefix, invalidating the entire thing. I've updated the gist to a new version to deal with them as well. https://gist.github.com/simpolism/302621e661f462f3e78684d96bf307ba

    full list of breaking attachments:

    Attachment What it does Why safe to suppress
    skill_listing Lists available slash commands Interactive/IDE feature
    todo_reminders Periodic "use TodoWrite" nudges Unnecessary in harness
    unified_tasks Task tracking state Unnecessary in harness
    changed_files Diffs of previously-read files Redundant (model sees tool results)
    dynamic_skill Scans skill directories No dynamic skills in harness
    nested_memory One-shot memory triggers Fires unpredictably
    async_hook_responses Intermittent hook feedback No hooks in harness
    diagnostics LSP diagnostics No LSP servers in CLI
    lsp_diagnostics LSP diagnostics No LSP servers in CLI
  10. junaidtitan commented on Apr 3, 2026

    @junaidtitan

    Prompt cache invalidation on resume happens because the session JSONL has mutated between sessions — Claude Code re-reads the full file and the cache prefix no longer matches.

    Cozempic v1.4.1 reduces cache churn by keeping sessions lean. The guard daemon prunes bloat continuously so the session file changes less between turns. The SessionStart hook also runs a cleanup pass before the first API call.

    pip install cozempic && cozempic init

    Run cozempic current --diagnose to check your cache hit rate — it now shows read vs creation tokens so you can see how efficiently caching is working.

  11. simpolism commented on Apr 4, 2026

    @simpolism

    @blois why was this ticket closed? Still seeing the same poor caching behavior with --resume/--continue on 2.1.92.

  12. github-actions commented on Apr 11, 2026

    @github-actions

    This issue has been automatically locked since it was closed and has not had any activity for 7 days. If you're experiencing a similar issue, please file a new issue and reference this one if it's relevant.

  13. locked as resolved and limited conversation to collaborators on Apr 11, 2026
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

    area:corearea:costbugSomething isn't workinghas reproHas detailed reproduction stepsplatform:macosIssue specifically occurs on macOS

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions