`claude -p` misses the prompt cache after a turn without thinking (Opus 5.5, Fable 5.1, Sonnet 5.5)

Status Open
Reported on v2.1.288
Maintainer reply None cached
Activity 0 comments · opened Oct 6, 2026

claude -p misses the prompt cache after a turn without thinking

On Opus 5.5, Fable 5.1 and Sonnet 5.5, claude -p does not read the previous turn (the user message, the system reminders attached to it — environment, model, date, session context — and the reply) from the prompt cache when that turn produced no thinking, so every resume or fork writes them again. The system prompt is still read; only the messages are missed. If the system prompt alone is below the cache minimum, the whole prompt is written again. When the turn has thinking, everything is read. Opus 5, Fable 5 and Sonnet 5 are not affected.

Observed on Claude Code 2.1.288 through 2.1.292 on 6 October 2026. Before that date the prefixes were read correctly, including on 2.1.289, which fails now. Related: #99833 (the same symptom, attributed there to 2.1.290).

Repro

Save both files in one folder and run python3 repro.py 20 claude-opus-5-5 from it. Each run of 20 trials makes at least 40 sessions, which appear in that folder's resume history, and uses about 30,000 tokens, mostly cache writes. Each trial asks either "Reply with OK." (no thinking) or a 7×7-digit product (thinking) at low effort, resumes once, and reports whether the resume read the whole first prompt, the system prompt only, or nothing from cache. A first turn that thinks when it shouldn't, or the reverse, is asked again, up to three times. Variables starting with CLAUDE or ENABLE_ are removed so a calling Claude Code session does not leak in; the settings file keeps memory, CLAUDE.md files and claude.ai MCP servers out. PAD=<lines> pads the system prompt with filler lines of about 8 tokens each; PAD=64 puts it over the 512-token cache minimum. Sonnet 5's minimum is 1,024, so it needs PAD=52 for the first prompt to be cached at all, or PAD=128 for the system prompt alone to pass the minimum.

repro-settings.json:

{"env": {"CLAUDE_CODE_DISABLE_AUTO_MEMORY": "1", "CLAUDE_CODE_DISABLE_CLAUDE_MDS": "1", "ENABLE_CLAUDEAI_MCP_SERVERS": "false"}}

This setup only keeps things minimal and free of waste; it doesn't affect the bug.

repro.py:

import json, os, random, re, subprocess, sys
from concurrent.futures import ThreadPoolExecutor

N, MODEL = int(sys.argv[1]), sys.argv[2]
SYSTEM = "." + "".join(f"\nFiller line {i}." for i in range(int(os.environ.get("PAD", 0))))
ENV = {k: v for k, v in os.environ.items() if not re.match("CLAUDE|ENABLE_", k)}

def claude(prompt, *resume):
    cmd = ["claude", "-p", "--model", MODEL, "--effort", "low", "--tools", "", "--system-prompt", SYSTEM, "--output-format", "json", "--setting-sources", "project,local", "--settings", "repro-settings.json", *resume, "--", prompt]
    out = json.loads(subprocess.run(cmd, env=ENV, capture_output=True, encoding="utf-8").stdout)
    return out[-1] if isinstance(out, list) else out

def trial(t):
    for _ in range(3):
        a = claude(f"Reply with OK. ({random.random()})" if t % 2 else f"What is {random.randrange(10**6, 10**7)} times {random.randrange(10**6, 10**7)}? Answer with the number only.")
        thought = a["usage"]["output_tokens_details"]["thinking_tokens"] > 0
        if thought != bool(t % 2):
            break
    read, ua = claude("another test", "--resume", a["session_id"])["usage"]["cache_read_input_tokens"], a["usage"]
    return thought, "whole prompt" if read >= ua["cache_read_input_tokens"] + ua["cache_creation_input_tokens"] else "system prompt only" if read else "nothing"

rows = list(ThreadPoolExecutor(10).map(trial, range(N)))
for th in (True, False):
    got = [r for x, r in rows if x == th]
    print(f"{'with' if th else 'without'} thinking ({len(got)}): " + ", ".join(f"{k} {got.count(k)}" for k in ("whole prompt", "system prompt only", "nothing")))

Example output

Claude Code 2.1.292, Windows 11:

$ python3 repro.py 20 claude-opus-5-5
with thinking (10): whole prompt 10, system prompt only 0, nothing 0
without thinking (10): whole prompt 0, system prompt only 0, nothing 10

$ PAD=64 python3 repro.py 20 claude-opus-5-5
with thinking (10): whole prompt 10, system prompt only 0, nothing 0
without thinking (10): whole prompt 0, system prompt only 10, nothing 0

$ python3 repro.py 20 claude-opus-5
with thinking (10): whole prompt 10, system prompt only 0, nothing 0
without thinking (10): whole prompt 10, system prompt only 0, nothing 0

View original on GitHub ↗