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

Đang mở
#93,490 4 bình luận 0 reaction 0 người được giao Xem trên GitHub
area:core area:cost bug has repro performance platform:macos
Ngôn ngữ chính
Python
Star
145k
Fork
23.1k
Chỉ số merge pull request
Chỉ số pull request đang chờ

Mô tả

### 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].

Hướng dẫn đóng góp

Chưa lập chỉ mục được hướng dẫn đóng góp cho kho mã nguồn này

Hướng nghiên cứu

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.

Do mô hình lập chỉ mục viết ra từ nội dung của issue.

Đánh giá

Lĩnh vực
api, cli, performance
Loại issue
Lỗi
Độ khó
4/5
Thời gian dự kiến
3-5 ngày
Mức độ hoạt động
Sôi nổi
Độ rõ ràng
Khá rõ ràng
Mức phù hợp với người mới
54/100

Nhận issue mới trong hộp thư của bạn

Bản tóm tắt ngắn những issue GitHub phù hợp với người mới.