anthropics / anthropics/claude-code
[FEATURE] Cache CLAUDE.md and rules across sessions: they are re-written to the prompt cache at every session start
- 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 requests](https://github.com/anthropics/claude-code/issues?q=is%3Aissue%20label%3Aenhancement) and this feature hasn't been requested yet
- [x] This is a single feature request (not multiple features)
### Problem Statement
Project context (CLAUDE.md, `.claude/rules/*`, the memory index) is never served from the prompt cache across sessions, even when it has not changed. It is injected into the first user message together with content that differs in every session (the git status snapshot, and the user's first prompt), and the only cache breakpoint in that region sits at the end of the message. Any change to the volatile part invalidates the stable part, so CLAUDE.md is paid for as a cache write (1.25x input price, 2x with the 1-hour TTL) at the start of every session. Only the system prompt and tool definitions (~24k tokens here) are reused across sessions.
Any project with a large, rarely changing CLAUDE.md and rules files pays a full cache write for all of it at every session start, instead of a read at ~0.1x. With many sessions per day this shows up in cost, rate-limit consumption and time-to-first-token.
Measured on Claude Code 2.1.268 (latest), Linux, `claude -p --model sonnet --output-format json`, in a throwaway git repo with a ~114 KB CLAUDE.md, consecutive runs well inside the cache window. Numbers are `.usage.cache_read_input_tokens` / `.usage.cache_creation_input_tokens`.
**Test 1: same prompt, only git status changes (a new untracked file is added before runs 2 and 4)**
| Run | Git status | cache_read_input_tokens | cache_creation_input_tokens |
|---|---|---|---|
| 1 | unchanged | 80,222 | 0 |
| 2 | changed | 23,925 | 56,308 |
| 3 | unchanged | 80,233 | 0 |
| 4 | changed | 23,925 | 56,319 |
**Test 2: git status unchanged, only the prompt text changes** ("Reply with just OK." -> "Reply with just YES.")
| Run | cache_read_input_tokens | cache_creation_input_tokens |
|---|---|---|
| A1 | 80,244 | 0 |
| A2 | 23,925 | 56,319 |
Test 2 is the realistic case: the first prompt differs in every real session, so the ~56k tokens of CLAUDE.md are rebuilt every time.
**Test 3: CLAUDE.md renamed to STABLE.md (not auto-loaded) and passed with `--append-system-prompt-file STABLE.md`**
| Run | cache_read_input_tokens | cache_creation_input_tokens |
|---|---|---|
| B1 (cold) | 0 | 80,096 |
| B2 (git changed + new prompt) | 66,942 | 13,157 |
| B3 (another new prompt) | 66,942 | 13,154 |
With the same content in the system prompt, it is read from cache across sessions regardless of git status or prompt text.
The 23,925 figure is constant across all runs and matches the system prompt plus tool definitions. Prompt caching is a prefix match, so anything that differs before the next breakpoint (git status, the typed prompt) invalidates CLAUDE.md, which sits in the same stretch. `--exclude-dynamic-system-prompt-sections` does not help: it moves dynamic sections into the same first user message where CLAUDE.md already lives.
Caveats: measured in print mode only; breakpoint placement is inferred from the usage numbers, not from inspecting raw requests.
### Proposed Solution
This is low-hanging fruit: an ordering change plus one cache breakpoint, no new user-facing feature.
1. Order the injected startup context from most stable to least stable: CLAUDE.md + rules files (rarely change) -> memory index (changes occasionally) -> git status, date, other per-session data -> the user's prompt.
2. Put a cache breakpoint at the end of the CLAUDE.md + rules block, and ideally a second one after the memory index, so a memory edit does not invalidate the rules.
Expected result: after the first session, a new session with unchanged CLAUDE.md/rules/memory reads them from cache and writes only the small volatile tail.
Things to weigh:
- **Breakpoint budget.** The API allows 4 breakpoints per request. If all 4 are already used (system/tools plus moving breakpoints on the conversation tail), one tail breakpoint is the cheaper one to give up: the tail is small and re-written every turn anyway, while the stable block is large and re-written every session.
- **Nested CLAUDE.md loaded mid-session.** Unaffected; these are appended to later messages and cached with the conversation tail as today.
- **TTL.** Where the 5-minute TTL is used, the isolated stable block could get the 1-hour TTL so it survives gaps between sessions. The write costs 2x instead of 1.25x, which pays off once a single later session reuses it. A 1-hour entry must come before any 5-minute entries, which the proposed ordering satisfies.
- **Subagents.** Subagents that load the same startup context likely rebuild it on every spawn for the same reason. Not measured here.
### Alternative Solutions
- **Move the startup-loaded CLAUDE.md/rules into the system prompt**, after the static part. Test 3 shows this works today, and it needs no extra breakpoint because the existing system breakpoint moves to the end of the appended block. Only the startup-loaded set should move: anything added to the system prompt later in a session (nested CLAUDE.md, `/memory` edits) would invalidate the whole conversation cache, so mid-session additions must stay in the message stream.
- **Current workaround:** keep the stable content out of CLAUDE.md and pass it with `--append-system-prompt-file `. This loses auto-loading and the CLAUDE.md conventions (`/memory`, `/init`, nested files, other tools that read CLAUDE.md), so it is a stopgap.
### Priority
Medium - Would be very helpful
### Feature Category
Performance and speed
### Use Case Example
1. A project has a large CLAUDE.md plus rules files that change a few times a month.
2. A developer starts many short sessions a day in it, each with a different first prompt, and the git status changes as they work.
3. Today, every session start writes the whole CLAUDE.md + rules block to the cache again at 1.25x-2x.
4. With the change, only the first session after an edit writes it; every other session reads it at ~0.1x and starts faster.
### Additional Context
Repro (self-contained, needs `git`, `jq`, `python3`):
```sh
d=$(mktemp -d) && cd "$d" && git init -q
python3 -c "print('# Project notes\n' + 'Keep functions small and tested. ' * 3500)" > CLAUDE.md # ~114 KB
run() { claude -p "$1" --model sonnet --output-format json \
| jq -c '.usage | {cache_read_input_tokens, cache_creation_input_tokens}'; }
run "Reply with just OK." # warms the cache
run "Reply with just OK." # identical request: everything read
run "Reply with just YES." # only the prompt differs: CLAUDE.md is written again
touch newfile; run "Reply with just YES." # only git status differs: written again
```
Related but distinct issues (both about per-session values inside the system prompt, not about stable context sharing the first message with volatile content): #92524, #78720.
Guide de contribution
Aucun guide de contribution indexé pour ce dépôt
Piste de recherche
Start by running the self-contained git, python3, jq, and claude -p reproduction to confirm the cache token behavior. Then locate the startup context assembly and cache-breakpoint handling; done means unchanged CLAUDE.md and rules are read from cache across sessions while volatile context is rebuilt, with the reported usage pattern verified.
Rédigé par le modèle d'indexation à partir du texte de l'issue.
Évaluation
- Stack technique
- git, shell
- Domaine
- cli, developer-experience, performance
- Type d'issue
- Fonctionnalité
- Difficulté
- 5/5
- Temps estimé
- Plus d'une semaine
- Activité
- Active
- Clarté
- Plutôt claire
- Accessibilité débutants
- 35/100