[BUG] Session forks (--fork-session / /branch) forfeit the entire conversation prompt cache: session-id-bearing scratchpad path in the system prompt (cache-diagnosis: system_changed), plus a fork-only history re-serialization residual

Open 💬 0 comments Opened Jul 13, 2026 by jdoughty04

Summary

Since the scratchpad-directory rollout (~June 25, 2026), every session-level fork
(--fork-session, /branch, desktop-app Fork — anything that mints a new session id)
invalidates the prompt cache for the ENTIRE conversation history. The fork's first
request re-writes the whole conversation at the 1-hour cache-write premium (2x input
price). A plain --resume of the same session reuses ~100%. Verified on v2.1.207
(Windows 11, subscription auth, Haiku specimens, --strict-mcp-config) with the
cache-diagnosis-2026-04-07 API beta.

Root cause 1 — the scratchpad path embeds the session id in the SYSTEM prompt

The "# Scratchpad Directory" section interpolates
<tmpdir>\claude\<project-slug>\<SESSION-ID>\scratchpad
into the cached system prompt. A fork mints a new session id, so the system prompt
differs in exactly that one line — byte-diff of captured parent-vs-fork requests shows
the scratchpad line as the SOLE difference — and the API returns
cache_miss_reason: { "type": "system_changed" }
for the fork's first request. Per the prefix rule (tools → system → messages), that one
line invalidates the entire conversation behind it.

Measured (parent ~54k-token prefix, 2 turns):

  • resume (same id): cache_read 53,652 / cache_creation 377
  • fork (new id): cache_read 35,513 / cache_creation 18,660 ← tools-only reuse
  • byte-identical fork replay: cache_read 54,166 / cache_creation 0 ← proves pure content-based caching

This is exactly the anti-pattern your cache-diagnostics doc warns about ("a timestamp,
request ID, or other per-request value interpolated into the system prompt") and the
engineering blog cautions against — but generated by Claude Code itself.

Root cause 2 — --exclude-dynamic-system-prompt-sections relocates, not fixes

With the flag (on parent and fork), the system prompt becomes byte-identical, but the
same session-id-bearing scratchpad path MOVES into the first user message
(messages[0], third content block). Diagnosis flips to
cache_miss_reason: { "type": "messages_changed" }
and the conversation still re-writes from messages[0] (no cache breakpoint exists inside
the first message, so even its unchanged leading blocks are unrecoverable).
Measured: fork-with-flag cache_read 41,525 / cache_creation 12,905 — the flag salvages
only the ~6k system block, a fixed amount that approaches 0% of the penalty as
conversations grow.

Root cause 3 — fork-only history re-serialization (residual)

A forked session re-serializes stored single-text-block user turns as plain strings
("content": "..."), while resume re-sends the original block form
("content": [{"type":"text",...}]). Byte-level captures show this divergence on the
fork path only. Even if the scratchpad line were stabilized, this residual would still
break message-prefix identity at the affected turn.

Impact

  • Forking a long conversation is the most expensive thing a user can do short of a model

switch: full re-write of history at 2x (1h-bucket) pricing, invisible to the user.

  • The prompt-caching doc's "Sessions you run in parallel in the same directory ...

read each other's cache" is no longer true post-scratchpad: every session now has a
unique system prompt. Open issues #66966 / #47098 (cross-session non-reuse of
byte-identical envelopes) are consistent with this class of defect — per-session
identifiers in cacheable prefix content (#66966's own data points at per-session
attachment IDs in the first-message envelope). Closed #41284 reported the same
symptom pre-scratchpad.

Suggested fixes (either of 1-2 alone is a major win; all three = forks reuse like resumes)

  1. Remove the session-scoped path from cached content: derive the scratchpad path from

stable material (e.g. per-project, not per-session), or move the section BELOW the
last cache breakpoint / into per-turn ephemeral context, or have forks inherit the
parent's scratchpad path.

  1. Serialize forked history identically to resumed history (canonicalize content-block

shape on both paths).

  1. Docs: state fork/branch cache behavior explicitly in the prompt-caching page's

invalidates/keeps lists, and update the "Cache scope" section for the scratchpad era.

Environment

Claude Code 2.1.207 (native build), Windows 11, subscription (OAuth), default settings
plus --strict-mcp-config for isolation. Repro is deterministic; capture scripts and
request/response byte diffs available on request.

View original on GitHub ↗