Repository navigation
Description
Preflight Checklist
- I have searched existing issues and this hasn't been reported yet
- This is a single bug report (please file separate reports for different bugs)
- I am using the latest version of Claude Code
What's Wrong?
The CLI embeds dynamic per-machine sections — including git status output — into the system prompt at every spawn. For headless spawn-per-turn integrations (claude -p --resume <id> launched fresh each turn by a host process), any change to the repo's status output between turns (a commit, a new untracked file, a deleted marker) changes the system prompt. The system prompt sits ahead of the entire conversation, so the whole cached prefix is invalidated: cache_read_input_tokens collapses to the ~21k shared-boilerplate floor and the full conversation history is re-created as cache_creation_input_tokens.
Measured across one workspace's full transcript history (~/.claude/projects/<workspace>/*.jsonl): 1,407 of 16,058 API calls (8.8%) show the full-collapse signature — 161,124,671 tokens re-created in collapse events across 183 sessions. This burns subscription usage-cap headroom and adds latency; for API-key users it re-bills entire conversation histories at cache-write rates.
This is not the tool-reordering resume bug (#42338 / #34629): it reproduces with zero tools, zero MCP servers, and a one-word reply, and it is deterministic on demand via git status churn alone.
What Should Happen?
A resumed turn should serve the prior conversation from cache: at call N, cache_read_input_tokens should equal the cumulative read + creation of every prior call (the healthy signature, which holds reliably from a non-repo cwd). A change in git status output between turns should not re-bill the entire conversation history — e.g., the dynamic sections could sit behind a cache breakpoint / after the stable prefix, or be frozen to session-start values for -p --resume continuations (the model already receives fresh state via tool use).
Error Messages/Logs
No error is emitted - the failure is silent re-billing, visible only in per-call usage.
Re-run on 2.1.212 (fresh minimal repo, untracked marker flipped between resumes;
per-call usage from --output-format json):
turn1 read=21,213 create=9,203 (session created)
turn2 read=21,213 create=9,516 expected read=30,416 -> FULL COLLAPSE (prior prefix re-created)
turn3 read=30,416 create=364 expected read=30,729 -> partial re-creation
turn4 read=30,729 create=109 healthy
turn5 read=30,780 create=108 healthy
Same-day control from a NON-repo cwd, identical churn in an --add-dir'd repo: 4/4 healthy exact-prefix reads.Steps to Reproduce
- Control (non-repo cwd): in an empty non-repo directory run
claude -p "Reply with exactly: ok" --output-format json, then 4–5 more turns with--resume <session_id>. Observe: every resume'scache_read_input_tokensexactly equals the prior cumulativeread + creation. 0/5 collapses. - Trigger (git repo cwd + churn):
git inita minimal repo (one committed README), run the same turn-1 command from inside it, then the same--resumeturns — but between each turn create or delete an untracked marker file (any change togit statusoutput). - Observe: resumes following a status change collapse —
cache_readfalls to the ~21k floor andcache_creationre-does approximately the entire prior cumulative total. 3/5 collapsed on 2.1.207; 1 full collapse + 1 partial re-creation across 4 churned resumes on 2.1.212. - Collapses occur only on turns where the status output actually changed since the prior spawn — matching the intermittent ~9–15% rate observed in production traffic.
Claude Model
Not sure / Multiple models
Is this a regression?
I don't know
Last Working Version
No response
Claude Code Version
2.1.212 (Claude Code)
Platform
Anthropic API
Operating System
Windows
Terminal/Shell
Non-interactive/CI environment
Additional Information
- Auth/platform detail: first-party Anthropic backend via Claude Max subscription OAuth login — no API key, no gateway/proxy. Windows 11, native binary install (not npx).
- Models: minimal repro on Haiku (model-independent, keeps repro cost trivial); the same collapse signature is observed with Opus across production traffic in the measured corpus.
--exclude-dynamic-system-prompt-sectionsmakes it WORSE for this use-case: it moves the dynamic sections into the first user message, which regenerates on every resume — 5/5 collapses on the same repro.- Related but distinct: Session resume (--continue) invalidates entire prompt cache, causes massive rate limit consumption #42338 and [BUG] Prompt cache regression in --print --resume since v2.1.69(?): cache_read never grows, ~20x cost increase #34629 (resume cache invalidation attributed to
deferred_tools_deltatool reordering, v2.1.69+). This mechanism needs no tools at all; the trigger is the git-status section of the system prompt. - Workaround verified locally: spawn the subprocess from a stable non-repo cwd (removes the git-status section entirely) with repo access preserved via
--add-dir <repo>— 0/4 collapses with deliberate status churn in the add-dir'd repo. Only viable for sessions that don't require a repo cwd.
Activity
jcll commentedon Aug 13, 2026
Corroborating this from a different trigger, and with a cost figure in subscription-cap units rather than raw tokens.
Same signature, no git-status change, not headless. Ordinary interactive session, resumed after a 34-minute pause. Working tree clean, no commit inside the pause window, nothing about the repo state differed. The collapse happened anyway, and it landed on the floor named in this issue:
cache_read= 21,994. That is the ~21k shared-boilerplate floor this issue describes, reached without thegit statustrigger and without-p. Whatever the dynamic-system-prompt instability is, it is not confined to headless spawn-per-turn integrations in dirty repos — a normal--resumereaches it too.The prompt did not expire, it changed: every write in the session used the 1-hour tier (
cache_creation: {ephemeral_1h_input_tokens: N, ephemeral_5m_input_tokens: 0}) and the gap was 34 minutes. The rebuilt prefix was also 74,544 tokens smaller than the one sent before the pause — 816,300 against 890,844. An expired cache re-writes identical content. This re-wrote different content. No compaction record anywhere in the transcript.What it cost, in the units a subscriber actually feels. The account's
weekly_scopedmeter (the Fable bar behind/usage) was sampled every 15 minutes across the whole window. It read 17% before the resume and 20% after. Calibrating that meter against 16 integer percent-crossings in the same window puts cache-write tokens at ~0.087 output-token-equivalents (worst-case residual 0.24 points at that weight, versus 2.24 points for an output-only model):~3% of a weekly cap, consumed by one sentence of typed input, for content that had already been sent and was still sitting in a live cache. Roughly one point per 294,000 tokens re-cached. Which means the cost scales with how much good work is already in the session: the longer and more valuable the conversation, the more expensive it is to ask it one more question. Ending a nine-hour session and having a single afterthought is the most natural thing a user does, and it is the worst-priced action available to them.
The part that makes this sting: the product already warns about expensive reopens — just not this one. When a session's context is large or stale, the CLI surfaces the offer to
/clearand start fresh. That safeguard fires exactly when a user would already suspect the cost. It stays silent here, in the one case where the user has every reason to believe the resume is cheap: short pause, well inside the advertised 1-hour TTL, cache demonstrably live one turn earlier. The guardrail covers the intuitive case and misses the counter-intuitive one.Detection is already implemented; only the surfacing is missing. The 2.1.229 binary carries a cache-diagnosis path —
[PROMPT CACHE BREAK],[PROMPT CACHE] cache deletion applied, cache read:, and a state comparator trackingsystemHash,toolsHash,cacheControlHash,perBlockHashes,messageHashesand abuildDiffContentcallback behind acacheDiagnosisflag. The CLI can already tell which block moved and what it will cost. It just doesn't say so before spending the user's cap.Minimum viable fix, in ascending order of effort:
Prior art, for whoever triages this. The interactive-resume form of this defect was filed as #43657 (April 2026) and refiled as #67497 (documenting that #43657 was closed by the inactivity bot on 2026-06-11 as
not_planned, not because it was fixed). #67497 was then itself closed as stale on 2026-07-21. #66005 is the same collapse via a different trigger (--resumedropping the session's--effort), still open. #40524 is the same token signature, closed.That is the same defect filed four times across five months, twice killed by inactivity sweep, never fixed. Adding this here rather than opening a fifth issue.
Environment: Claude Code 2.1.229 (native, macOS 27.0), model
claude-fable-5, subscription (1-hour cache TTL), interactive--resume, git repo with a clean working tree. Session transcript with per-recordversionstamps and fullusageblocks available if useful.fix(server): keep the Claude CLI alive across Stop
shangaslammi commentedon Sep 6, 2026
Confirming on Linux (WSL2), Claude Code 2.1.263, Haiku 4.5, Max subscription (1-hour cache tier), from a headless spawn-per-turn host. Three additions that I did not see covered above.
1.
--resume --fork-sessionis affected identically, and the fork's system prompt is built at the first turn, not at process spawn. A fork process started with--input-format stream-jsonbefore a tree change, and given its first user turn after it, is cold. A fork started and turned right after that one is warm. So a host cannot "reserve" a warm fork by spawning early.2. Concurrent first turns of several forks race the cache write and all go cold. Two forks of the same parent turned at the same time after a commit both read the ~6.3k floor and both re-created ~5.5k; a third fork turned after they finished read the full prefix with zero creation. Sequencing the first one and then fanning out avoids it, but nothing in the docs suggests this is needed.
Per-call
usageof the first assistant message (read / creation), all forks from one bootstrap session in a minimal repo:git commitNote the clean commit case: nothing in
git status --shortchanged, only thegit log -n 5lines, and that is enough.3. A long-lived
--input-format stream-jsonprocess is unaffected, which is a better workaround than the non-repo cwd for hosts that can keep the process alive. Same repo, same three turns, untracked file added before turn 2 and a clean commit before turn 3:--resume(read / creation)The idle process makes no calls between turns,
--json-schemaapplies per turn, and a stdincontrol_requestinterrupt leaves it usable and warm. The downside is that it is only available to hosts that own the process; interactive--resumeand every fork still pay.Minimal repro (bash, Haiku, a few cents):
I could not find a switch: in the 2.1.263 binary the block is skipped only when the cwd is not a git repository, and
CLAUDE_CODE_DISABLE_GIT_INSTRUCTIONSgoverns something else. Freezing the git section to session-start values for-p --resume/--fork-session, or placing it after the message history, would fix both the resume and the fork cases.github-actions commentedon Nov 12, 2026
Closing for now — inactive for too long. Please open a new issue if this is still relevant.