anthropics / anthropics/claude-code

[BUG] Prompt-caching regression since 2.1.237: hook/reminder context is uncacheable again

Đang mở
#89,651 1 bình luận 0 reaction 0 người được giao Xem trên GitHub
area:core area:cost area:hooks bug has repro regression
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](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`.

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

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.

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

Đánh giá

Công nghệ
node.js, python
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
45/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.