anthropics / anthropics/claude-code
[BUG] --resume never hits the prompt cache past the static prefix on Fable 5.1 (opus hits): session-start context message is replayed as a plain string instead of the content blocks it was sent with
- Langage dominant
- Python
- Étoiles
- 145k
- Forks
- 23.1k
- Métriques de merge des PR
- Métriques de PR en attente
Description
### Preflight Checklist
- [x] I have searched existing issues and this hasn't been reported yet (#91971 has the symptom without a cause, #83913 the same mechanism for a different message, #44045 is closed and covered messages[0])
- [x] This is a single bug report
- [x] I am using the latest version of Claude Code
### What's Wrong?
every `--resume` (headless and interactive) re-writes the whole conversation into the cache even when the cache is warm. cache_read stays flat at the tools+system floor, cache_creation is the rest of the context, every single time.
i captured the requests with a local proxy and diffed a fresh session against its own resume. tools identical, system blocks identical, messages[0] identical (all 4 blocks, byte for byte). the first difference is messages[1], the `role: system` message that carries the SessionStart hook output plus the `# Environment` block:
- fresh start: `content` is an array with one text block, `cache_control: {type: ephemeral, ttl: 1h}`
- resume: `content` is a plain string. same text, same length (26285 chars), no cache_control
so the prefix stops matching right there and everything behind it gets written again. on a small probe thats ~18-25k tokens, on my real session it was a 385k re-write (a /background fork of a long session: read 27k, wrote 385k).
this is not TTL, not settings, not hooks. two fresh launches 30s apart hit each others cache completely (231238 read, 0 written), so the client can produce a stable prefix, it just doesnt on the resume path.
### What Should Happen?
a rebuilt history reproduces the request that was already sent, byte for byte, same carrier, same block shape, same cache_control. thats what #83913 already asks for PreToolUse/PostToolUse context, this is the same class for the session-start context message. #91971 has this exact symptom on chained `-p --resume` but no cause, i think this is the cause.
### Error Messages/Logs
no error, only the counters. structure of both captured requests (lengths + sha256 prefixes, no content):
```
fresh interactive start (first request):
tools: 30 defs, sha 3ad5cd726882
system[0]: 70 chars, sha 6b1c6e45dfc2, cache_control=None
system[1]: 57 chars, sha 2719b7a469d9, cache_control={'type': 'ephemeral', 'ttl': '1h'}
system[2]: 907 chars, sha a11f50f6b3db, cache_control=None
system[3]: 9983 chars, sha 75bb0c61bbc0, cache_control={'type': 'ephemeral', 'ttl': '1h'}
messages[0] role=user: content=ARRAY[4]: text 25023 sha ca6fa325c3d5 cc=None | text 1272 sha 75082207bd89 cc=None | text 347 sha 6c524f1b0cfd cc=None | text 30 sha d85f47a7c1a1 cc=None
messages[1] role=system: content=ARRAY[1]: text 26285 sha 33946e993933 cc={'type': 'ephemeral', 'ttl': '1h'}
interactive --resume 53s later (first request):
tools: 30 defs, sha 3ad5cd726882
system[0]: 70 chars, sha 6b1c6e45dfc2, cache_control=None
system[1]: 57 chars, sha 2719b7a469d9, cache_control={'type': 'ephemeral', 'ttl': '1h'}
system[2]: 907 chars, sha a11f50f6b3db, cache_control=None
system[3]: 9983 chars, sha 75bb0c61bbc0, cache_control={'type': 'ephemeral', 'ttl': '1h'}
messages[0] role=user: content=ARRAY[4]: text 25023 sha ca6fa325c3d5 cc=None | text 1272 sha 75082207bd89 cc=None | text 347 sha 6c524f1b0cfd cc=None | text 30 sha d85f47a7c1a1 cc=None
messages[1] role=system: content=STRING, 26285 chars, sha 33946e993933
messages[2] role=assistant: content=ARRAY[1]: text 2 sha 2689367b205c cc=None
messages[3] role=user: content=ARRAY[4]: text 246 sha 35c19d240c4d cc=None | text 129 sha c24432f46b9b cc=None | text 62 sha f8b4bb9c329c cc=None | text 36 sha 8daab17abfe0 cc=None
messages[4] role=system: content=ARRAY[1]: text 614 sha bcb751e6ee5d cc={'type': 'ephemeral', 'ttl': '1h'}
```
same hashes everywhere up to and including messages[1], only the shape of messages[1] differs.
### Steps to Reproduce
```bash
echo '{"mcpServers":{}}' > /tmp/empty-mcp.json
claude -p "Reply with the single word ok." --output-format json --strict-mcp-config --mcp-config /tmp/empty-mcp.json
# take session_id from the result, then within a few seconds:
claude -p "Reply with the single word ok again." --output-format json --strict-mcp-config --mcp-config /tmp/empty-mcp.json --resume
claude -p "Reply with the single word ok a third time." --output-format json --strict-mcp-config --mcp-config /tmp/empty-mcp.json --resume
```
what i get (`result.usage`), direct to the api, tool search on (default):
| call | cache_read | cache_creation |
|---|---|---|
| fresh | 12887 (tools+system were already cached from an earlier run) | 24383 |
| resume 1 | 12887 | 24644 |
| resume 2 | 12887 | 24907 |
with mcp servers on its the same shape, just bigger (12887 read / ~32k written on every resume, 4 resumes in a row).
note on the capture: the request diff below was taken through a local proxy (custom ANTHROPIC_BASE_URL), and in that mode tool search is off so all tools are inlined and there is no deferred-tools listing in messages[0]. in that capture messages[0] is byte-identical and the first divergence is messages[1]. direct to the api cache_read is even lower (12887 = tools+system only), so in normal mode messages[0] very likely differs as well (the deferred-tools listing block, see #49038 / #44045). i could not capture that one without disabling tool search.
interactive is the same:
fresh `claude` in a terminal, one prompt, `/exit`, `claude --resume ` 53s later, one prompt:
| | cache_read | cache_creation |
|---|---|---|
| fresh | 0 | 67824 |
| resume | 48825 | 19376 |
same diff: first divergence is messages[1], blocks with cache_control on fresh, plain string on resume.
the diff (content replaced by lengths):
fresh:
```json
{"role":"system","content":[{"type":"text","text":"<26285 chars: SessionStart hook additional context ... # Environment ...>","cache_control":{"type":"ephemeral","ttl":"1h"}}]}
```
resume:
```json
{"role":"system","content":""}
```
### Claude Model
fable 5.1 (claude-fable-5-1)
### Is this a regression?
dont know. first measured on 2.1.268, no earlier version measured for this exact path.
### Last Working Version
unknown
### Claude Code Version
2.1.268
### Platform
claude.ai subscription (Max), first party api, deferred tools on (default), promptCacheTtl and subagentPromptCacheTtl both 1h
### Operating System
macOS (Darwin 25.3.0, arm64)
### Terminal/Shell
iTerm2, zsh (headless repro is shell independent)
### Additional Information
two fresh launches 30s apart read each others cache in full (231238 read, 0 written), so the prefix is stable across processes and only the resume replay changes the shape of messages[1].
Guide de contribution
Aucun guide de contribution indexé pour ce dépôt
Piste de recherche
Reproduce the issue with the documented `claude -p ... --resume ` commands and compare the captured fresh and resumed requests. Start at the resume history-replay path, focusing on messages[1], and consider the work done when the resumed request preserves its content-block array and `cache_control` so stable context is read from the prompt cache instead of rewritten.
Rédigé par le modèle d'indexation à partir du texte de l'issue.
Évaluation
- Domaine
- api, cli, performance
- Type d'issue
- Bug
- Difficulté
- 4/5
- Temps estimé
- 3-5 jours
- Activité
- Active
- Clarté
- Plutôt claire
- Accessibilité débutants
- 54/100