Claude Media
statuslineでprompt_cacheを表示する — hit_ratioとwarmでキャッシュ劣化を確認

statuslineでprompt_cacheを表示する — hit_ratioとwarmでキャッシュ劣化を確認

Claude Codeのstatuslineが運ぶprompt_cacheフィールドをjqで取り出し、hit_ratio・warm・missesでプロンプトキャッシュの劣化をひと目で確認するスクリプトをまとめます。

Claude Code v2.1.251以降のstatuslineは、prompt_cacheというオブジェクトを運びます。セッションのメイン会話がプロンプトキャッシュをどれだけ有効に使えているかを、warmhit_ratiomissesなどのフィールドにまとめたものです。既存のstatuslineスクリプトに数行足すだけで、キャッシュが冷めかけているサインを常時表示に組み込めます。プロンプトキャッシュ自体がClaude Codeの利用上限にどう効くかは別記事で扱っており、本記事はその効き具合を読み取る側の話です。

prompt_cacheフィールドで何が分かるか

prompt_cacheは、Claude Codeがメイン会話のAPIレスポンスから計算するキャッシュ統計です。プロバイダー側から返るキャッシュトークン数をもとに集計するため、どのプロバイダー経由でも同じ形式で動きます。サブエージェントのリクエストはこの集計に含まれません。

フィールドが現れるのは、メイン会話で最初のAPIレスポンスが返った後です。セッション開始直後のstatuslineには出ないため、prompt_cacheが空でも故障ではなく、まだ会話が1往復もしていないだけの可能性があります。

公式ドキュメントが挙げるフィールドは次の14個です。

フィールド内容
warm内容キャッシュされたプレフィックスがTTL内かどうか
caching_observed内容このセッションで一度でもキャッシュトークンが報告されたか
ttl内容現在のキャッシュのライフタイム("5m"または"1h")
expires_at内容キャッシュがTTLを外れて冷える時刻(エポック秒)
requests内容メイン会話で記録されたAPIリクエスト数
misses内容キャッシュにあった内容を再処理したリクエスト数
expected_rebuilds内容compactionやツール結果クリアに伴うキャッシュ再構築の回数
hit_ratio内容全入力トークンに占めるキャッシュ読み取りトークンの割合(0〜1)
cache_write_tokens内容このセッションでキャッシュに書き込まれた総トークン数
miss_recache_tokens内容miss扱いのリクエストがキャッシュに書き戻したトークン数
last_miss_at内容直近のmissが発生した時刻(エポック秒)
last_miss_cause内容直近のmissの推定原因(v2.1.260以降)
miss_causes内容原因別に集計したmiss件数(v2.1.260以降)
recache_tokens_if_cold内容キャッシュが冷めた場合に次のリクエストが再キャッシュするトークン数

すべてを表示すると1行に収まらないため、公式ドキュメントも「短いstatuslineなら通常1〜2個を表示する」とし、warmhit_ratioを最もキャッシュ状態を要約するフィールドに挙げています。

warmhit_ratioを組み込む

まずは最小構成です。warmtruefalseかで絵文字を出し分け、hit_ratioをパーセント表示にします。hit_ratioはキャッシュ読み取り・書き込み・非キャッシュ入力のいずれもゼロのときはnullになるため、// emptyでフォールバックします。

~/.claude/statusline.sh
#!/bin/bash
input=$(cat)
 
MODEL=$(echo "$input" | jq -r '.model.display_name')
WARM=$(echo "$input" | jq -r '.prompt_cache.warm // empty')
HIT=$(echo "$input" | jq -r '.prompt_cache.hit_ratio // empty')
 
if [ -n "$HIT" ]; then
  HIT_PCT=$(printf '%.0f' "$(echo "$HIT * 100" | bc -l)")
  if [ "$WARM" = "true" ]; then
    ICON="🟢"
  else
    ICON="🔵"
  fi
  echo "[$MODEL] ${ICON} cache ${HIT_PCT}%"
else
  echo "[$MODEL]"
fi

warmfalseでもhit_ratio自体は消えません。warmはキャッシュされたプレフィックスが今TTL内かどうかを示すだけで、セッションを通じた累計のhit_ratioは残ります。そのため上のスクリプトでは、warmを色分けの材料に、hit_ratioを数値の本体に使い分けています。

missesrequestsを足すと、このセッションのリクエストのうち、何回がキャッシュを外したかが見えます。

~/.claude/statusline.sh
MISSES=$(echo "$input" | jq -r '.prompt_cache.misses // 0')
REQUESTS=$(echo "$input" | jq -r '.prompt_cache.requests // 0')
[ "$REQUESTS" -gt 0 ] && echo "cache: ${MISSES}/${REQUESTS} miss"

動作確認には、公式ドキュメントの擬似入力の手法がそのまま使えます。標準入力にJSONを直接流し込めば実機を用意せずに試せます。

echo '{"model":{"display_name":"Opus"},"prompt_cache":{"warm":true,"hit_ratio":0.91,"misses":2,"requests":14}}' | ~/.claude/statusline.sh

missesとlast_miss_causeで劣化の原因を見る

hit_ratioが下がっていること自体は分かっても、原因までは分かりません。missesが1以上あるセッションでは、last_miss_causeを読むと直近のmissが何によって起きたかが分かります。

last_miss_causecausesという配列を持ち、主な原因名としてtools_changedsystem_prompt_changedttl_expired_5mlikely_server_sideなどが入ります。原因を特定できなかったときはnullです。tools_changedが原因のときはtools_addedtools_removedにツールの増減数が、system_prompt_changedが原因のときはsystem_char_deltaにシステムプロンプトの文字数の変化量が、それぞれ添えられます。

~/.claude/statusline.sh
CAUSE=$(echo "$input" | jq -r '.prompt_cache.last_miss_cause.causes[0] // empty')
[ -n "$CAUSE" ] && echo "last miss: $CAUSE"

tools_changedが絡んだ回数は、ツールの増減数まで踏み込んで表示できます。

~/.claude/statusline.sh
TOOLS_ADDED=$(echo "$input" | jq -r '.prompt_cache.last_miss_cause.tools_added // empty')
TOOLS_REMOVED=$(echo "$input" | jq -r '.prompt_cache.last_miss_cause.tools_removed // empty')
[ -n "$TOOLS_ADDED" ] && echo "tools: +${TOOLS_ADDED} -${TOOLS_REMOVED}"

同様にsystem_prompt_changedが原因のときは、system_char_deltaでシステムプロンプトの増減幅を確認できます。MCPサーバーの追加・削除や、CLAUDE.mdの編集がどれだけ影響したかを数値で追えます。

~/.claude/statusline.sh
CHAR_DELTA=$(echo "$input" | jq -r '.prompt_cache.last_miss_cause.system_char_delta // empty')
[ -n "$CHAR_DELTA" ] && echo "system prompt delta: ${CHAR_DELTA} chars"

いつ起きたmissなのかはlast_miss_atが持っています。expires_atと同じくエポック秒なので、dateコマンドで人間が読める形式に変換します。セッションにまだmissが無ければnullです。

~/.claude/statusline.sh
LAST_MISS=$(echo "$input" | jq -r '.prompt_cache.last_miss_at // empty')
[ -n "$LAST_MISS" ] && echo "last miss at: $(date -r "$LAST_MISS" '+%H:%M:%S')"

miss_causesはセッション全体を通じた原因別の集計です。{"tools_changed": 2}のように、原因名をキーにしたオブジェクトで返ります。1回ごとの原因はlast_miss_cause、セッションを通じた傾向はmiss_causesという役割分担です。ツールの増減が頻発するセッションならtools_changedが積み上がり、メッセージの間隔が5分以上空くことが多いセッションならttl_expired_5mが積み上がる、という違いが出ます。

ttlexpires_atでキャッシュが冷める前に気づく

ttlは現在のキャッシュされたプレフィックスが持つライフタイムで、"5m""1h"のどちらかを返します。expires_atはそのプレフィックスがTTLを外れて冷める時刻をエポック秒で示し、直近のレスポンスがキャッシュトークンを報告していなければnullになります。

spend_limitresets_atと同様、expires_atもそのままでは読みにくいためdateコマンドで変換すると実用的です。

~/.claude/statusline.sh
expires_at=$(echo "$input" | jq -r '.prompt_cache.expires_at // empty')
if [ -n "$expires_at" ]; then
  now=$(date +%s)
  remaining=$((expires_at - now))
  [ "$remaining" -gt 0 ] && echo "cache expires in ${remaining}s"
fi

残り秒数はカウントダウンのように見えますが、statuslineの再実行はイベント駆動です。メインセッションがサブエージェントの完了待ちなどで待機している間はこのイベントが止まるため、settings.jsonrefreshIntervalを設定しないと表示が固まったままになります。なお更新自体は300ms単位でデバウンスされるため、短時間に連続するイベントは1回にまとめて反映されます。ただしスクリプトのコマンド自体を変更したときは、このデバウンスを飛ばして即座に新しいコマンドが実行されます。

settings.json
{
  "statusLine": {
    "type": "command",
    "command": "~/.claude/statusline.sh",
    "refreshInterval": 5
  }
}

残り時間が短くなっているのに会話を再開する予定があるなら、キャッシュが冷める前に次のメッセージを送るという選択肢があります。冷めた後は、recache_tokens_if_cold分を再キャッシュすることになります。recache_tokens_if_coldを合わせて表示すると、冷めた場合にどれだけのトークンを再キャッシュすることになるかが具体的な数値で分かります。この値はcompactionやツール結果クリアの直後はnullになり、次のリクエストが新しい会話サイズを記録するまで埋まりません。

~/.claude/statusline.sh
RECACHE=$(echo "$input" | jq -r '.prompt_cache.recache_tokens_if_cold // empty')
[ -n "$RECACHE" ] && echo "if cold: ${RECACHE} tokens"

caching_observedはこれらすべての前提になるフィールドで、セッション中に一度でもキャッシュトークンが報告されていればtrueのままです。falseのときは、プロンプトキャッシュそのものがオフか、利用しているプロバイダーやgatewayがキャッシュ統計を返していないことを意味します。warmfalseでもcaching_observedtrueなら「キャッシュ機能自体は効いているが今は冷めている」、caching_observed自体がfalseなら「そもそも計測できていない」という切り分けになります。

cache_write_tokensmiss_recache_tokensでコスト影響を見積もる

cache_write_tokensはセッション最初の書き込みも含めた、キャッシュへの総書き込みトークン数です。これに対してmiss_recache_tokensは、missesと判定されたリクエストだけがキャッシュに書き戻したトークン数を指します。前者はセッション全体の必要経費、後者は本来避けられたはずの再処理という違いがあります。

miss_recache_tokenscache_write_tokensに占める割合が大きいセッションほど、キャッシュ設計を見直す余地が大きいと読めます。最初の1回だけキャッシュを書き込み、その後は安定して読み取れているセッションなら、miss_recache_tokensはほぼゼロのままcache_write_tokensだけが増えていきます。

~/.claude/statusline.sh
WRITE=$(echo "$input" | jq -r '.prompt_cache.cache_write_tokens // 0')
RECACHE_MISS=$(echo "$input" | jq -r '.prompt_cache.miss_recache_tokens // 0')
[ "$WRITE" -gt 0 ] && echo "recache: ${RECACHE_MISS}/${WRITE} tokens"

ここまでのフィールドを1つのスクリプトにまとめると、キャッシュの状態を1行で要約できます。

~/.claude/statusline.sh
#!/bin/bash
input=$(cat)
 
MODEL=$(echo "$input" | jq -r '.model.display_name')
WARM=$(echo "$input" | jq -r '.prompt_cache.warm // empty')
HIT=$(echo "$input" | jq -r '.prompt_cache.hit_ratio // empty')
MISSES=$(echo "$input" | jq -r '.prompt_cache.misses // 0')
CAUSE=$(echo "$input" | jq -r '.prompt_cache.last_miss_cause.causes[0] // empty')
 
if [ -n "$HIT" ]; then
  HIT_PCT=$(printf '%.0f' "$(echo "$HIT * 100" | bc -l)")
  [ "$WARM" = "true" ] && ICON="🟢" || ICON="🔵"
  LINE="${ICON} cache ${HIT_PCT}%"
  [ "$MISSES" -gt 0 ] && LINE="${LINE} (${MISSES} miss"
  [ -n "$CAUSE" ] && LINE="${LINE}: ${CAUSE}"
  [ "$MISSES" -gt 0 ] && LINE="${LINE})"
  echo "[$MODEL] $LINE"
else
  echo "[$MODEL]"
fi

warmだけを見る運用と比べると、この構成は「今キャッシュが効いているか」だけでなく「今のセッションを通じてどれだけ効いてきたか」と「直近で外れたなら何が原因か」までを1行に押し込めます。表示が長くなりすぎると感じる場合は、missesが0のときだけ短縮表示にする、上のスクリプトのように条件分岐で調整します。

/usageコマンドとの違い

同じ集計は/usageコマンドのセッションブロックにもPrompt cache (main)という行で表示されます。

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)

statuslineのprompt_cache/usagePrompt cache (main)行は、同じ内部集計を異なる面に出しているだけで、数値そのものは一致します。使い分けの軸は「常時見るか、必要なときだけ開くか」です。

表示内容確認できるタイミング
statuslineのprompt_cache表示内容自作スクリプトで任意の形式・任意のフィールドを表示確認できるタイミング毎ターン、statuslineの更新トリガーごと
/usagePrompt cache (main)表示内容requests・hit率・miss回数・再キャッシュ量・warm状態を1行にまとめて表示確認できるタイミングコマンドを実行したとき

長時間のセッションでキャッシュの効きを継続的に監視したいならstatusline、compactionの直前後など特定のタイミングだけ詳しく見たいなら/usageが向きます。両方を併用しても矛盾は起きません。同じv2.1.251で追加されたフィールドにはrate_limits.spend_limitもあり、こちらもstatuslineと/usageの両方に出る点は共通しています。

導入時に押さえておく前提

prompt_cachecontext_windowとは別のオブジェクトです。context_window.current_usageが現在の1リクエストの内訳を表すのに対し、prompt_cacheはセッション開始からの累積です。両方を表示に使う場合、current_usage.cache_read_input_tokensは「直近のリクエストで読めたキャッシュ量」、prompt_cache.hit_ratioは「セッションを通じた読み取り比率」という違いを取り違えないようにします。

prompt_cacheが持つexpires_atが更新トリガーにもなる点も見落としやすいところです。statuslineの再実行は新しいアシスタントメッセージの到着や/compact完了だけでなく、warmなキャッシュが直近のexpires_atに到達した瞬間にも起きます。warmtrueからfalseへ切り替わる表示は、この仕組みによってリアルタイムに近い形で反映されます。

prompt_cacheはメイン会話専用の集計で、サブエージェントのリクエストは含まれません。並行してサブエージェントを走らせるセッションでは、statuslineのprompt_cacheだけを見てもサブエージェント側のキャッシュ効率までは分かりません。

まとめ

prompt_cacheは、Claude Codeのstatuslineがv2.1.251以降で運ぶプロンプトキャッシュの集計フィールドです。短い表示ならwarmhit_ratioの2つで劣化の有無をひと目で示せ、原因まで踏み込みたいときはmisseslast_miss_cause(v2.1.260以降)を足します。同じ数値は/usagePrompt cache (main)行でも確認できるため、常時監視はstatusline、ピンポイントの確認は/usageという使い分けが現実的です。context_window.current_usageの直近値とセッション累計のprompt_cacheを混同しない、この1点を押さえておくと数値の解釈がぶれません。

この記事を共有:XはてブLinkedIn