はじめに
このブログの投稿作業自体、daily_post_runner.shからclaude -pを呼び出す無人パイプラインで動いている。2026-08-27の記事ではpromptCacheTtlでキャッシュの寿命を延ばす方法を扱ったが、そのときは「なぜミスしたか」までは分からなかった。
GitHubのCHANGELOG.md(https://github.com/anthropics/claude-code/blob/main/CHANGELOG.md)のv2.1.260(2026-09-03公開)には次の1行がある。
Added a likely cause for prompt-cache misses (e.g. tool definitions or system prompt changed, idle past the TTL) to
/costand the status line’sprompt_cachefield
公式ドキュメントcode.claude.com/docs/en/costsにはもう少し詳しい仕様が書かれている。/usageのSession blockにPrompt cache (main)という行が追加され(v2.1.251以降)、ミスの「たぶんの原因」まで表示されるようになった(v2.1.260以降)というものだ。
After the main conversation’s first API response, Claude Code also adds a
Prompt cache (main)line to the Session block, summarizing the session’s prompt cache use: the request count, the share of input tokens from cache, cache misses, and whether the cache is warm right now.When Claude Code can identify a likely cause for the last miss, the line names it too, for example
likely cause: tool definitions changed. The likely-cause text requires Claude Code v2.1.260 or later.
表示例(ドキュメントより引用):
Prompt cache (main): 14 requests · 91% of input tokens from cache · 2 misses (last 6m 10s ago, 310.2k tokens re-cached) · 1 expected rebuild (compaction or tool-result clearing) · warm (1h TTL, last activity 40s ago)
code.claude.com/docs/en/statuslineにはステータスライン向けのJSONスキーマも載っていて、prompt_cache.last_miss_cause.causesにはtools_changed・system_prompt_changed・ttl_expired_5m・likely_server_sideのいずれかが入るとされている。
これは自動化パイプラインを運用している側からすると、まさに欲しかった機能に見えた。「今日のキャッシュミスはツール定義の変更のせいか、TTL切れのせいか」を自動判定してくれるなら、コスト調査が一気に楽になる。手元のバージョンはv2.1.258で、Prompt cache (main)行自体の要求バージョン(v2.1.251)はすでに満たしているが、原因表示(v2.1.260)には届いていない。ちょうどよい対照実験の材料なので、実際にclaude -pで叩いて、このブログ自身の運用形態(無人・ヘッドレス・--resumeを挟む複数プロセス)でこの機能がどう見えるかを検証した。
結論を先に書く。この機能は、このブログのような無人-pパイプラインには実質的に届かない。 バージョンの問題ではなく、(1) サブスクリプションプランのアカウントでは/usageの出力形式自体が公式ドキュメントの例と違う、(2) ステータスライン機構はヘッドレスモードで一切呼ばれない、(3) --resumeを挟んだ別プロセスの-p呼び出しではキャッシュ統計がリセットされる、という3つの理由が重なっているためだ。
手順: 実機で段階的に切り分ける
検証は次の順で進めた。
- まず無料・短時間でできる範囲(バージョン確認と1回だけの
/usage呼び出し)を試す。 - 1プロセス内で複数ターンを流し、実際にキャッシュヒット・ミスが起きる状況を作ってから
/usageを呼ぶ(--input-format stream-jsonで1プロセスを維持する)。 npm install --prefixでv2.1.260を隔離導入し、同じ実験をやり直してバージョンが原因かどうかを切り分ける。- ステータスラインのコマンドが実際に呼ばれているかを、ファイルに書き込むだけのダミースクリプトで確認する。
- 公式の診断が使えない前提で、生の
usageJSONから自分でミス判定する簡易スクリプトを作る。
コピペ用プロンプト集
プロンプト1: バージョンと/usageの一発確認(無料・数秒)
前提: claudeコマンドが導入済み。API呼び出しは発生しない。
claude --version
claude -p "/usage" --output-format json | uv run python -c "
import json,sys
d=json.load(sys.stdin)
print('cost:', d.get('total_cost_usd'), 'duration_ms:', d.get('duration_ms'))
print(d.get('result','')[:200])
"
期待結果の例(実測、v2.1.258・サブスクリプションプラン):
2.1.258 (Claude Code)
cost: 0 duration_ms: 401
You are currently using your subscription to power your Claude Code usage
Current session: 16% used · resets Sep 8 at 11:40am (Asia/Tokyo)
成功判定: costが0であること(/usage自体はAPI呼び出しを伴わないローカル集計)。出力の中にPrompt cacheという文字列が含まれるかどうかを確認する。含まれていなければ、後述するプロンプト2以降で「本当に無いのか、単に材料不足なのか」を切り分ける価値がある。
プロンプト2: 1プロセス内で複数ターンを流し、/usageにキャッシュ実績を渡す
前提: 実際のAPI呼び出しを2回行う(実費、目安1回あたり0.05〜0.12ドル程度)。--resumeではなく--input-format stream-jsonで1つのclaudeプロセスを維持し続ける点がポイント。
mkdir -p /tmp/cc-cache-test && cd /tmp/cc-cache-test
cat > turns.jsonl << 'EOF'
{"type":"user","message":{"role":"user","content":[{"type":"text","text":"1+1は?"}]}}
{"type":"user","message":{"role":"user","content":[{"type":"text","text":"2+2は?"}]}}
{"type":"user","message":{"role":"user","content":[{"type":"text","text":"/usage"}]}}
EOF
cat turns.jsonl | claude -p --input-format stream-json --output-format stream-json --verbose \
> stream_out.jsonl
grep -o '"result":"[^"]*"' stream_out.jsonl | tail -1
期待結果(実測、v2.1.258):
"result":"You are currently using your subscription to power your Claude Code usage\n\n...(中略、プラン使用率と行動傾向の要約)...\n\nbreakdown · sonnet: 100% · cache hit: 74%"
成功判定: 出力の末尾にbreakdown · <モデル名>: 100% · cache hit: NN%という行が付くこと(実プロセス内でキャッシュ実績が発生した証拠)。同時に、ドキュメントが説明するPrompt cache (main): N requests · ...という形式の行、およびlikely cause: ...の文言が出力のどこにも含まれないことを確認する(grep -c "Prompt cache" stream_out.jsonlが0になることで判定できる)。実測ではcache hit: 74%という数字自体は、stream_out.jsonl内の各ターンのcache_read_input_tokens(22211→45460)とcache_creation_input_tokens(23249→50)から計算した比率(67671÷(67671+23299)≒74.4%)と一致しており、集計自体は動いている。表示形式だけがドキュメントの例と異なる。
プロンプト3: v2.1.260を隔離導入して、バージョンが原因かを切り分ける
前提: npmが使え、ネットワーク接続があること。既存のclaude本体には触らない。
npm install @anthropic-ai/claude-code@2.1.260 --prefix /tmp/cc260 --no-save --silent
CC260=/tmp/cc260/node_modules/.bin/claude
cat > turns2.jsonl << 'EOF'
{"type":"user","message":{"role":"user","content":[{"type":"text","text":"5+5は?"}]}}
{"type":"user","message":{"role":"user","content":[{"type":"text","text":"6+6は?"}]}}
{"type":"user","message":{"role":"user","content":[{"type":"text","text":"/usage"}]}}
EOF
cat turns2.jsonl | "$CC260" -p --input-format stream-json --output-format stream-json --verbose \
> stream_out_260.jsonl
grep -c "Prompt cache" stream_out_260.jsonl
grep -o "breakdown[^\"]*cache hit: [0-9]*%" stream_out_260.jsonl | tail -1
期待結果(実測):
0
breakdown · sonnet: 100% · cache hit: 74%
成功判定: grep -c "Prompt cache"が0のままであること。v2.1.260はlast_miss_cause診断そのものの要求バージョンを満たしているにもかかわらず、同じ「サブスクリプションプランの/usageヘッドレス出力」という条件では結果が変わらなかった。つまりこれはバージョン未対応の問題ではなく、実行条件(プランの種類、またはヘッドレス実行そのもの)に起因する制約だと分かる。どちらが真因かは、APIキー(Console契約)のアカウントで同じ実験をしていないため断定できない。
プロンプト4: ステータスラインがヘッドレスモードで本当に呼ばれるか確認する
前提: プロンプト2の環境。ステータスラインに設定したコマンドは、受け取ったJSONをファイルに追記するだけの単純なものにする。
cd /tmp/cc-cache-test
cat > statusline_probe.sh << 'EOF'
#!/bin/bash
cat >> /tmp/cc-cache-test/statusline_calls.jsonl
EOF
chmod +x statusline_probe.sh
cat > settings_probe.json << 'EOF'
{"statusLine": {"type": "command", "command": "/tmp/cc-cache-test/statusline_probe.sh"}}
EOF
rm -f statusline_calls.jsonl
claude -p --settings settings_probe.json "3+3は?" --output-format json > /dev/null
ls -la statusline_calls.jsonl 2>&1
期待結果(実測):
ls: statusline_calls.jsonl: No such file or directory
成功判定: ファイルが作られていないこと。--settingsで指定したstatusLineコマンドは、対象の-p呼び出し(実際にAPIコールが成功し課金も発生した)の間、一度も実行されなかった。ドキュメントはprompt_cacheオブジェクトを「ステータスラインが受け取るJSON」の一部として説明しているが、そのステータスライン自体がヘッドレスモードで起動しない以上、この経路からlast_miss_causeを取得する方法は無い。これは推測ではなく、ファイルの有無という単純な事実で確認できる。
プロンプト5: 公式診断の代わりに、生のusage JSONで自分でミスを判定する
前提: プロンプト2で作ったstream_out.jsonlが手元にあること。ドキュメントが定義する「5%かつ2,000トークン以上を再処理したらミス」というルールをそのまま使う。
cd /tmp/cc-cache-test
uv run python -c "
import json
turns = []
for line in open('stream_out.jsonl'):
line = line.strip()
if not line:
continue
d = json.loads(line)
if d.get('type') == 'result' and d.get('usage'):
u = d['usage']
turns.append({
'cache_read': u.get('cache_read_input_tokens', 0),
'cache_creation': u.get('cache_creation_input_tokens', 0),
})
prev_available = None
for i, t in enumerate(turns):
if prev_available and prev_available > 0:
ratio = t['cache_creation'] / prev_available
is_miss = ratio > 0.05 and t['cache_creation'] >= 2000
print(f'turn{i}: cache_creation={t[\"cache_creation\"]} '
f'ratio_vs_prev={ratio:.1%} -> {\"MISS\" if is_miss else \"HIT\"}')
prev_available = t['cache_read'] + t['cache_creation']
"
期待結果(実測):
turn1: cache_creation=50 ratio_vs_prev=0.1% -> HIT
turn2: cache_creation=0 ratio_vs_prev=0.0% -> HIT
成功判定: turn0(最初のターン)を除いた各ターンについて、HIT/MISSの判定が出ること。この例では2ターン目・3ターン目(/usageはAPI呼び出しなしなので数値は0)ともHITと判定され、実際にcache_read_input_tokensが45460(前ターンの再利用可能トークン全量)まで伸びていた実測と整合する。公式のlikely_miss_cause(tools_changedなど原因の種類まで)はこの方法では分からないが、「ミスが起きたかどうか」だけなら--output-format jsonの生データと閾値計算だけで、ヘッドレスパイプラインでも自前検知できる。
発見したこと(つまずき所)
--resumeを挟んだ別プロセスの-p呼び出しでは、キャッシュ統計がゼロに戻る。 最初にclaude -p "1+1は?"で作ったセッションを、直後にclaude -p --resume <session_id> "/usage"という別プロセスで呼び出すと、その/usage実行のusageは{"input_tokens":0,"cache_creation_input_tokens":0,"cache_read_input_tokens":0}、total_cost_usdは0だった。prompt_cache統計は「現在のプロセスの主要な会話」のAPI応答から計算される仕様なので、会話履歴そのものはディスクから正しく再開されていても、キャッシュ統計だけはプロセスをまたぐと引き継がれない。cronなどで1ターンごとにclaude -p --resumeを叩き直す設計のパイプラインでは、この意味での/usage診断は原理的に機能しない。- サブスクリプションプランの
/usageヘッドレス出力は、ドキュメントの例とは別形式だった。 ドキュメントのPrompt cache (main): N requests · ...という行は、少なくとも今回のPro/Maxサブスクリプションアカウント・ヘッドレス実行の組み合わせでは一度も出力されず、代わりに利用率の要約とbreakdown · <モデル>: 100% · cache hit: NN%という簡略な行になっていた。ドキュメントにはこの違いについて明記が無いため、原因がプラン種別によるものか、ヘッドレス実行そのものによるものかは私の実験だけでは断定できない(誠実性のため未確認と明記する)。 - 関連する既知の問題としてGitHub Issue #91151(open、2026-09-01作成)がある。 これは「Agent SDK経由の
--resumeを使ったヘッドレスワークロード(Bedrock)で、会話が8万トークンを超え1分以上の間隔が空くと、キャッシュがシステム+ツール定義だけの床まで完全に潰れる」という、私の検証より大規模な実データに基づく報告だ。今回の私の実験はこの規模には遠く及ばないが、「ヘッドレス・--resume運用でのキャッシュの振る舞いは見た目通りに信頼できない」という同じ方向の懸念を裏付けている。 - もう1件、Issue #84904(open)は「ステータスライン経由でしか配信されない情報(
rate_limitsなど)は、ステータスラインを描画しない実行形態(デスクトップアプリなど)からは到達できない」と指摘している。 今回prompt_cache.last_miss_causeについて確認したのと同じ構造の「配信経路の穴」で、ステータスライン専用の新機能は今後もヘッドレス側に届かない可能性がある、という一般化した注意点として紹介する(自分で再現確認したのはprompt_cache側のみ)。 total_cost_usdは--output-format stream-jsonでは累積値になる。 プロンプト2の実験で、1ターン目のtotal_cost_usdが0.097ドル、2ターン目が0.107ドル(差分0.0093ドル)だったのは、この値がプロセス開始からの累積コストだからだ(--output-format jsonの単発呼び出しでは逆に1回分のみ)。自分でコスト集計スクリプトを書く際にここを混同すると2重計上・過小計上のどちらも起こりうる。この挙動の食い違いはGitHub Issue #83239(open)がドキュメント不備として報告している。
活用例: ヘッドレス運用でのコスト診断の組み立て方
- 無人
-pパイプラインで「なぜ高くついたか」を追いたい場合、/usageやステータスラインの新診断には頼らず、--output-format json(またはstream-json)の生のusageフィールドを自分のログに残し、プロンプト5のような閾値判定を自前で回す設計にする。 - 1回のcronサイクルの中で複数ターンのやり取りが必要なら、ターンごとに
--resumeで別プロセスを起動するより、--input-format stream-jsonで1プロセスを維持したほうが、少なくともプロセス内キャッシュ統計は一貫して積み上がる(公式診断行が出るかは別として、生データの追跡はしやすい)。 total_cost_usdを自前集計に使うときは、--output-formatの種類によって累積か単発かが変わる点を先に確認する(プロンプト2・3の実測を参照)。
注意点・限界
- 今回の検証はすべてPro/Maxサブスクリプションのアカウントで行っており、API キー(Claude Console)契約のアカウントでは試していない。ドキュメントの
Prompt cache (main)行の表示例が実際にどちらの契約形態で出るのかは、公式ドキュメントの記述だけでは判別できず、断定を避けた。 - TTL切れ(
ttl_expired_5m)や実際のツール定義変更(tools_changed)を意図的に発生させてlast_miss_causeの値そのものを見る、というところまでは今回到達していない。表示経路そのものがヘッドレス・サブスクリプションの組み合わせで見当たらなかったため、その先の原因判定の精度までは検証できていない。 - 最新リリースのv2.1.263(2026-09-06公開)は、CHANGELOG.md上は”Bug fixes and reliability improvements”とだけ書かれており、今回のテーマに関わる個別項目の記載は無い。
まとめ
Claude Code v2.1.260で追加された「プロンプトキャッシュ・ミスの原因表示」は、ドキュメント通りに読むと/usageとステータスラインの両方から使えそうに見える。しかし、このブログ自身が使っているような無人・ヘッドレス・-pベースのパイプラインで実機検証すると、ステータスライン経路はそもそも起動せず、/usageはサブスクリプションプランでは別形式の出力になり、--resumeを挟んだ別プロセス呼び出しではキャッシュ統計自体がリセットされる、という3つの理由で実質的に機能しなかった。ヘッドレス運用でキャッシュ由来のコストを追いたい場合は、公式の新診断を待つのではなく、生のusage JSONを自分のログに残して閾値判定する設計を今のうちに用意しておく方が確実だ。
参考情報
本記事のテーマはGitHub CHANGELOG.md/Releases(https://github.com/anthropics/claude-code/blob/main/CHANGELOG.md、https://github.com/anthropics/claude-code/releases)の直近の変更点確認を起点に選定した。事実確認には合わせてcode.claude.com/docs/en/costsとcode.claude.com/docs/en/statuslineを用いた。YouTube「AI大学」チャンネルは今回参照していない。


Comments