anthropics / anthropics/claude-code
[BUG] Prompt-caching regression since 2.1.237: hook/reminder context is uncacheable again
- Vorherrschende Sprache
- Python
- Sterne
- 145k
- Forks
- 23.1k
- PR-Merge-Kennzahlen
- PR-Kennzahlen ausstehend
Beschreibung
### Preflight Checklist
- [x] I have searched [existing issues](https://github.com/anthropics/claude-code/issues?q=is%3Aissue%20state%3Aopen%20label%3Abug) and this hasn't been reported yet
- [x] This is a single bug report (please file separate reports for different bugs)
- [x] I am using the latest version of Claude Code
### What's Wrong?
Since Claude Code **2.1.237**, headless (`claude -p`) sessions serialize SessionStart-hook `additionalContext` and the CLI's reminder blocks as a trailing `role: "system"` message whose `content` is a plain string.
The plain-string form cannot carry `cache_control`. Because this is the trailing message, the content lands after the final cache breakpoint and is billed again as uncached `input_tokens` at every session start.
This is a regression of the fix for [anthropics/claude-agent-sdk-python#1120](https://github.com/anthropics/claude-agent-sdk-python/issues/1120). The fix released in **2.1.212** encoded this message as a block array carrying `cache_control`, keeping the content inside the cached prefix. That behavior remained intact through **2.1.236**; **2.1.237** changed it back to a plain string.
The regression appears to come from the change described in 2.1.237's changelog as:
> Fixed prompt caching for sessions using an LLM gateway or custom base URL.
### What Should Happen?
The trailing system message should carry `cache_control` using the block-array representation, as it did from `2.1.212` through `2.1.236`. Alternatively, the hook/reminder content should remain within the cached prefix.
### Error Messages/Logs
```shell
Captured request analysis for the last good and first bad releases:
== OLD: npx -y @anthropic-ai/claude-code@2.1.236
messages[1][0] role=system: text len=14832 [cache_control] <-- HOOK CONTEXT
VERDICT: hook context is INSIDE the final cache_control prefix (cacheable)
== NEW: npx -y @anthropic-ai/claude-code@2.1.237
messages[1] role=system (plain string): text len=14862 <-- HOOK CONTEXT
VERDICT: hook context is AFTER the final cache_control breakpoint (uncacheable)
The same plain-string form is still present in `2.1.245`, which was `@latest` at the time of testing.
```
### Steps to Reproduce
the standalone reproduction requires no API key:
Run:
```bash
python3 repro_cache_regression.py \
--old-bin "npx -y @anthropic-ai/claude-code@2.1.236" \
--new-bin "npx -y @anthropic-ai/claude-code@2.1.237"
```
The script creates a synthetic SessionStart hook, points `ANTHROPIC_BASE_URL` at a local capture sink, and records each CLI version's outgoing `/v1/messages` request before the intentionally failed API call.
The output shows that `2.1.236` places the hook/reminder context inside the final cache breakpoint, while `2.1.237` places the same context after it.
Full runs, including `@latest`, are available in [repro.md](https://github.com/leoavelino/share_files/blob/main/002_claude_headless_cache_regression_2/repro.md).
### Claude Model
None
### Is this a regression?
Yes, this worked in a previous version
### Last Working Version
S
### Claude Code Version
2.1.245
### Platform
Anthropic API
### Operating System
macOS
### Terminal/Shell
Terminal.app (macOS)
### Additional Information
### Version boundary
- **Last good:** `2.1.236`
- **First bad:** `2.1.237`
- **Still affected:** `2.1.245` (`@latest` at the time of testing)
- **Verified good:** `2.1.213`, `2.1.221`, `2.1.229`, and `2.1.231` through `2.1.236`
- **Verified bad:** `2.1.237`, `2.1.241`, and `2.1.245`
The issue reproduces with `claude-sonnet-5` in print mode. The billing effect is visible across all model families in our environment.
### Billing confirmation
Using the same protocol as the linked `claude-agent-sdk-python#1120` issue, run two fresh `claude -p` sessions back-to-back so that the first warms the cache and the second measures steady state.
On the second run:
| Version | `input_tokens` |
|---|---:|
| `2.1.236` | `2` |
| `2.1.241` | approximately `4,500` |
For both versions:
```text
input_tokens
+ cache_read_input_tokens
+ cache_creation_input_tokens
= 22,425
```
The prompt did not grow. The same tokens moved out of the cached buckets and into full-price `input_tokens`.
Beitragsleitfaden
Für dieses Repository ist kein Beitragsleitfaden indexiert
Rechercherichtung
Start with the standalone repro_cache_regression.py script and run it with the 2.1.236 and 2.1.237 commands to compare captured /v1/messages requests. Trace the claude -p request serialization that creates the trailing system message; done means hook and reminder context uses the block-array form with cache_control and the reproduced input-token regression is gone.
Vom Indexierungsmodell aus dem Issue-Text verfasst.
Bewertung
- Tech-Stack
- node.js, python
- Bereich
- api, cli, performance
- Issue-Typ
- Bug
- Schwierigkeit
- 4/5
- Geschätzter Aufwand
- 3-5 Tage
- Aktivitätsstatus
- Aktiv
- Klarheit
- Größtenteils klar
- Anfängerfreundlichkeit
- 45/100