[BUG] Forking from unprimed post-compact context re-bills the full shared prefix twice (fork AND parent each pay full cache_creation)

Status Open
Reported on v2.1.247
Maintainer reply None cached
Activity 1 comment · opened Aug 27, 2026

What's Wrong?

A fork's value proposition is inheriting the parent's warm prompt cache. But when /fork is the first action after /compact + a model switch — i.e. before any API call has primed the post-compact context into the cache — the shared prefix gets billed at full cache_creation price twice:

  1. The fork's first API call writes the entire shared prefix as new cache (cache_creation_input_tokens: 52798, cache_read_input_tokens: 8859 — only the small system-prefix portion hits).
  2. Two minutes later, the parent's own next turn (after the fork returns) does not reuse the cache the fork just wrote: cache_creation_input_tokens: 53647, cache_read_input_tokens: 8859 (1h ephemeral). The parent rebuilds essentially the same ~53k prefix from scratch.

The fork's cache write of the shared conversation prefix is evidently not placed (or not breakpointed) so the parent can read it back, even though both contexts share the identical post-compact history and the parent's turn ran only ~2 minutes after the fork's first call — well within both 5m and 1h cache TTLs, on the same model.

Sequence that produced it:

  1. Long session, /compact runs.
  2. Model switched (opus → other model) immediately after the compact, before any message is sent — so the post-compact context exists only client-side; no API call has primed it into any cache for the new model.
  3. /fork <prompt> issued as the first message.
  4. Fork runs to completion (16 API calls; its own subsequent calls cache normally — cache_read grows monotonically from 61657 after the first call).
  5. Parent's next turn after the fork returns pays full cache_creation for the same prefix again.

Net effect: ~53k tokens of identical context billed as cache_creation twice within ~2 minutes, instead of once (or, ideally, zero extra times had the parent primed its prefix before the fork inherited it).

What Should Happen?

Either (or both) of:

  • Prime before fork: when /fork is issued on context that has never been sent to the API (post-compact, post-model-switch), prime the parent's prefix first so the fork genuinely inherits a warm cache — that inheritance is the stated point of forking.
  • Reusable fork write: place cache breakpoints such that the fork's first call's write of the shared conversation prefix is readable by the parent's next turn. The shared history is byte-identical up to the fork point; only the tail (fork directive vs. fork-return notification) diverges, so a breakpoint at the shared-prefix boundary would let the parent read ~53k instead of rewriting it.

Expected usage shape for the parent's post-fork turn: cache_read ≈ 53k, cache_creation ≈ small delta — not the inverse.

Error Messages/Logs

# Parent transcript (jq over assistant turns; deduped; timestamps UTC)

# Last parent turn before the event (model A = opus-tier):
2026-08-27T05:09:22  model=claude-opus-5   cache_read=61656  cache_creation=4051

# (user runs /compact, switches model, then /fork as the first message at 05:31:54)

# Fork's own transcript — first API call (model B, unprimed context):
2026-08-27T05:32:00  model=claude-fable-5  cache_read=8859   cache_creation=52798
# Fork's second call — its own write is read back fine *within* the fork:
2026-08-27T05:32:04  model=claude-fable-5  cache_read=61657  cache_creation=804
# ... fork continues caching normally through 05:33:56 (16 calls total)

# Parent's first turn after the fork returns — full rebuild of the same prefix:
2026-08-27T05:34:03  model=claude-fable-5  cache_read=8859   cache_creation=53647
# (usage detail shows cache_creation: { ephemeral_1h_input_tokens: 53647, ephemeral_5m_input_tokens: 0 })

Note: 8859 is this session's stable system-prefix hit baseline (appears identically on both the fork's and the parent's cold calls); 52798 vs 53647 differ only by the divergent tails (fork directive vs. /fork command record + task-return notification).

Steps to Reproduce

  1. Run a session long enough to have meaningful context, then /compact.
  2. Immediately switch the model (before sending any message), so the post-compact context is unprimed for the new model.
  3. Issue /fork <some task> as the very first message after the compact/switch.
  4. Let the fork complete, then send any message in the parent (or let a task notification trigger a parent turn).
  5. Inspect ~/.claude/projects/<project>/<session>.jsonl and <session>/subagents/agent-<fork-id>.jsonl: compare message.usage.cache_creation_input_tokens / cache_read_input_tokens on (a) the fork's first call and (b) the parent's first post-fork turn. Both show full-size cache_creation for the same prefix; the parent's cache_read stays at the system-prefix baseline.

Claude Model

Other — parent ran claude-opus-5 pre-fork; fork and post-fork parent turns ran claude-fable-5 (model switched between compact and fork, which is what left the context unprimed).

Is this a regression?

I don't know

Claude Code Version

2.1.247 (Claude Code)

Platform

Anthropic API

Operating System

Ubuntu/Debian Linux

Terminal/Shell

Other (tmux)

Additional Information

  • Both the fork's first-call write and the parent's post-fork write are for the same post-compact conversation history; the two contexts diverge only after the fork point. With ~2 minutes between them, same model, 1h-ephemeral cache in play, the parent reading the fork's write back should be structurally possible if a cache breakpoint sits at (or before) the shared-prefix boundary.
  • The fork itself is otherwise healthy: from its second call on, cache_read grows monotonically (61657 → 84494 over 15 calls) — the problem is confined to the fork/parent boundary, in both directions (fork inherits nothing; parent reuses nothing).
  • Fork transcripts live at <project>/<session-id>/subagents/agent-<fork-task-id>.jsonl, and the same entries appear inline in the parent .jsonl as isSidechain: true, which is how the two usage sequences above were extracted.

View original on GitHub ↗

This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗