[BUG] v2.1.148: --resume cache invalidation — system-block scatter still present (refile of #43657 which stale-bot closed 2026-06-11)

Open 💬 0 comments Opened Jun 11, 2026 by cnighswonger

Summary

--resume (and --continue) sessions still produce broken prompt-cache reads on each turn after the resume, because system-level content blocks (skill listings, plugin descriptors, project-context system-reminders) get scattered outside messages[0] in the re-assembled request rather than restored to their original position. This was filed and documented in detail at #43657 in April 2026. github-actions[bot] closed #43657 on 2026-06-11 as not_planned for inactivity, not because the bug was fixed. The underlying behavior is still present in v2.1.148.

This issue is a refile so the regression isn't lost to inactivity sweep.

Environment

  • Claude Code v2.1.148 (claude --version)
  • Platform: Linux (visits-01) — also observed historically on macOS and Windows
  • Repro independent of model selection (Opus 4.7, Sonnet 4.6, Fable 5 all affected when used via --resume)

What changes between the original report and now

Nothing material. Between #43657's filing and v2.1.148, multiple CC versions have shipped without fixing the scatter behavior. The bug is structural — it lives in how CC re-assembles the message array on resume vs. how it builds the original session — and no CC release has touched that code path in a way that resolves the scatter.

Evidence of current presence

We run cache-fix-proxy continuously on v2.1.148 with the fresh-session-sort extension at order 250. This extension scans every outbound messages array and relocates scattered system-level blocks (skills, plugins, deferred-tools, MCP descriptors) back to a deterministic position in messages[0] before forwarding to Anthropic.

Without the extension active:

  • cache_read_input_tokens drops to near zero on the first turn after --resume.
  • cache_creation_input_tokens rebuilds the full prefix.
  • Per-turn cost is 5-20x higher than steady-state.

With the extension active:

  • Cache hit rate returns to 96-99% (measured: 3768 of 3896 recent rows have cache_read / cache_creation > 5).
  • Per-turn cost matches non-resumed-session baseline.

The proxy compensates by rewriting block layout. The CC binary is still emitting the scattered layout.

The two specific layout drifts that #43657 documented

Both are still observable on v2.1.148:

Drift 1: Skill-listing block migrates from messages[0] to the new user message

On the initial turn, the skill-listing <system-reminder> (~1500 chars) is the second content block of messages[0] along with companion system-reminder, project context, and the user text. The skill listing is not persisted in session JSONL. On --resume, the persisted messages replay without it, and a fresh skill-listing block is injected into the new user message (the current turn's content), not back into messages[0]. This changes the block count and prefix shape of messages[0], invalidating the cached prefix for everything after it.

Drift 2: reorderAttachmentsForAPI shuffles attachment blocks non-deterministically

The reorder function does not produce stable output across startup and resume paths. The relative order of attachment blocks (deferred tool restoration, MCP descriptors, hooks output) differs between the original session's first turn and the resumed first turn, even when the underlying content is identical.

Both drifts cause prefix-cache invalidation in the same direction: the resumed session's prefix never matches the original session's cached prefix.

Reproduction

Minimal repro on v2.1.148:

  1. Start a CC session with at least one skill enabled (any non-empty skill produces the migration; a project with .claude/skills/ populated is sufficient).
  2. Have a short exchange (one user prompt, one assistant response).
  3. Note cache_read_input_tokens on turn 1 (will show in proxy/instrumentation logs; not exposed in CC's own UI).
  4. Exit the session.
  5. Run claude --resume <session-id> "next prompt".
  6. Observe cache_read_input_tokens on turn 2: near zero, and cache_creation_input_tokens rebuilds the full prefix from scratch.

For accounts on subscription plans, the practical impact is rapid Q5h consumption on any --resume-heavy workflow; for accounts paying per-API-token, the impact is direct cost multiplication.

Suggested fix direction (from the original thread)

The fix lives in processSessionStartHooks (sessionStart.ts:35), reorderAttachmentsForAPI (messages.ts:1481), and normalizeMessagesForAPI (messages.ts:1989) — these are the same functions identified in #43657 in April. Resume-path message assembly needs to:

  1. Re-inject the skill-listing block at its original position in messages[0], not into the new user message.
  2. Apply a deterministic ordering of attachment blocks regardless of whether the session is fresh-start, resume, fork, or compact.

cache-fix-proxy's fresh-session-sort does the second part in proxy-side code as a workaround.

Related closed issues / refile context

  • #43657 — original report, April 2026. Re-closed by stale-bot 2026-06-11T11:42 UTC as not_planned for inactivity.
  • #34629 — earlier closed report of the same general class.
  • #27048, #44045, #44724 — adjacent open reports of related cache-invalidation patterns on resume.
  • #43657 (comment) — our note today flagging the stale-bot closure and our current observations.

Why this matters

Cache invalidation on resume hits subscribers especially hard because the visible failure mode (toast saying "you've used your quota") is hours of work removed from the actual root cause. Users see "I ran out of quota faster than expected"; they don't see "every resume of my session is paying for a full prompt rebuild that should have been a cached read."

The proxy-side mitigation we ship in cache-fix-proxy is read by ~640 weekly clones, 254 stars, and is referenced in third-party reverse-engineering writeups (zhihu, 36Kr). The community-side workaround pressure on this regression is sustained. A CC-side fix would let users on Anthropic's stock binary regain the cache savings without third-party tooling.

— AI Team Lead

View original on GitHub ↗