Claude Codeのプロンプトキャッシュ—TTLの決まり方と$0.17の内訳

claude -p "hello" を1回投げるだけで $0.174971 かかりました。返ってきたのは挨拶1行。金額の92%はプロンプトキャッシュへの書き込みです。

目次

hello一言に$0.17かかる内訳

リクエスト1回のトークン内訳を取ります。--output-format json を付けると使用量が JSON で返る。計測環境は Claude Code v2.1.251、モデル Opus 5、Claudeサブスクのプラン内です。

cd /tmp && claude -p "hello" --output-format json | jq '.usage'
jq: error (at <stdin>:1): Cannot index array with string "usage"

公式ドキュメントは「usage.cache_creation を読む」と書いていますが、--output-format json が返すのはオブジェクトではなく配列です。中身を見ると4要素あります。

claude -p "hello" --output-format json | jq -c '[.[].type]'
["system","assistant","rate_limit_event","result"]

使用量が入っているのは末尾の result です。.[-1] で取り直します。

claude -p "hello" --output-format json | jq '.[-1].usage'
{
  "input_tokens": 2,
  "cache_creation_input_tokens": 16065,
  "cache_read_input_tokens": 9922,
  "output_tokens": 374,
  "cache_creation": {
    "ephemeral_1h_input_tokens": 16065,
    "ephemeral_5m_input_tokens": 0
  },
  "service_tier": "standard"
}

素の入力は 2トークンhello の分だけです。残りの 16065 トークンはキャッシュへの書き込み、9922 トークンはキャッシュからの読み込み。書き込みが ephemeral_1h_input_tokens に全部乗っているので、1時間TTLで書かれたと分かります。

3行の掛け算でtotal_cost_usdに一致する

Opus 5 の入力単価は $5/MTok、出力は $25/MTok。キャッシュの単価は入力単価に倍率をかけて出します。API リファレンスの “Pricing” セクションが定める倍率は、5分TTLの書き込みが 1.25倍、1時間TTLの書き込みが 2倍、読み込みは TTL に関係なく 0.1倍

項目トークン単価金額
素の入力2$5/MTok$0.000010
1時間TTLの書き込み16,065$10/MTok($5×2)$0.160650
キャッシュ読み込み9,922$0.50/MTok($5×0.1)$0.004961
出力374$25/MTok$0.009350
合計$0.174971

API が返した total_cost_usd0.174971。小数第6位まで一致します。$0.17 のうち $0.16 は応答の対価ではなく、次のターンのために 16065 トークン分の下地を作った代金です。

TTLを5分に落とすと$0.12になる

同じ挨拶を5分TTLで投げ直します。

CLAUDE_CODE_PROMPT_CACHE_TTL=5m claude -p "hello" --output-format json \
  | jq '.[-1] | .usage.cache_creation, .total_cost_usd'
{
  "ephemeral_1h_input_tokens": 0,
  "ephemeral_5m_input_tokens": 17524
}
0.12408350000000001

書き込みが ephemeral_5m_input_tokens に移り、単価が $10/MTok から $6.25/MTok に下がりました。17524 × $6.25/MTok = $0.109525、読み込み 10047 × $0.50/MTok = $0.005024、出力 381 × $25/MTok = $0.009525。素の入力2トークンを足すと 0.1240835 で、こちらも返り値と一致します。

ただ5分が常に安いわけではありません。読み込みの 0.1倍は TTL によらず同じなので、5分以内に次を投げ続ける限り安いのは5分TTL。1時間TTLが効くのは席を外したあとです。期限切れのプレフィックスを作り直す代金は、読み込みの 0.1倍ではなく書き込みの 1.25倍か2倍で請求されます。

キャッシュは3層のプレフィックスで並ぶ

Claude Code の公式ドキュメント “How Claude Code uses prompt caching” の “How the cache is organized” が、リクエストの並び順を3層で説明しています。

中身変わるタイミング
システムプロンプトコア指示、ツール定義、output styleロード済みツール定義の集合が変わる、Claude Code がアップグレードされる
プロジェクトコンテキストCLAUDE.md、auto memory、スコープなしのルールセッション開始時、/clear/compact の後
会話ユーザーのメッセージ、Claudeの応答、ツール結果毎ターン

API はリクエストの先頭(プレフィックス)を照合してキャッシュを引きます。照合は完全一致。プレフィックスのどこか1箇所が変われば、そこから後ろは全部計算し直しです。ファイル単位やセグメント単位のキャッシュは存在しません。

会話層の変化は上2層を巻き込まない

変わりにくいものを先頭に置いてある理由がこれ。毎ターン伸びるのは会話層だけなので、システムプロンプトとプロジェクトコンテキストはキャッシュに残ります。逆にシステムプロンプトが1文字変われば、後ろに続く全部が別のプレフィックス扱いになる。

モデルとeffortは本文に入らないのに鍵の一部

プロンプトのテキストではないのにキャッシュキーに含まれるものが2つあります。モデルと effort レベル。

  • /model でモデルを切り替えると、内容が同じでも会話履歴を全部読み直します
  • /effort で effort を変えたときも同じ。ただし今と同じレベルに設定し直した場合、Claude Code はキャッシュを保ったまま適用します
  • opusplan は plan mode で Opus、実行時に Sonnet へ解決されるため、モードを出入りするたびにモデル切替が起きます

fast mode もリクエストヘッダがキャッシュキーに入るので、ONにした最初のターンだけ会話全体が uncached で請求されます。しかもその分は fast mode の料金で計算されるため、長いセッションの終盤でONにするほど高くつきます。料金体系と使えない条件は Claude Code fast modeとは—/fastの料金と使えない時の対処 にまとめました。

TTLは2つのバケットで別々に決まる

“Which TTL each request gets” セクションは、Claude Code が投げるリクエストを2つのバケットに分けています。

  • main conversation: 対話ターン、非対話の -p 実行、Agent SDK のターン、それらとインラインで走るヘルパー
  • それ以外: サブエージェント、Workflow、in-process の teammate、fork、compaction、セッションタイトルの生成

どちらのTTLになるかは、課金の種類でも変わります。

バケットClaudeサブスク(プラン内)使用量クレジット / APIキー / クラウドプロバイダ
main conversation1時間5分
それ以外5分(サーバー側が制御するヘルパーのみ1時間)5分

使用量クレジットに入ると勝手に5分へ落ちる

プランの使用量上限を超えて usage credits を使い始めると、その分は実費請求になるため、Claude Code は main conversation を安い5分TTLへ切り替えます。設定を何も触っていなくても、上限を跨いだ瞬間に挙動が変わる。1時間を維持したいなら明示的に指定する必要があります。

冒頭の実測で ephemeral_1h_input_tokens に乗っていたのは、サブスクのプラン内で動いていたからです。APIキー認証なら何も設定しなくても5分になります。

FORCE_PROMPT_CACHING_5Mが他の全部に勝つ

TTLを自分で決める入口は、設定キーと環境変数の両方が用意されています。main conversation が promptCacheTtlCLAUDE_CODE_PROMPT_CACHE_TTL、それ以外が subagentPromptCacheTtlCLAUDE_CODE_SUBAGENT_PROMPT_CACHE_TTL。受け付ける値は 5m1h だけで、他を書いても Claude Code は無視します。どちらも v2.1.242 以降で使えます。

{
  "promptCacheTtl": "1h",
  "subagentPromptCacheTtl": "1h"
}

複数の指定がぶつかったときの解決順は “Choose the TTL yourself” セクションが定義しています。

  1. FORCE_PROMPT_CACHING_5M=1(両バケットを5分に固定)
  2. バケットの環境変数
  3. バケットの設定キー
  4. サブエージェントの experimental.cacheTtl frontmatter(v2.1.248以降)
  5. ENABLE_PROMPT_CACHING_1H=1(両バケットに1時間を要求)
  6. バケットのデフォルト

1番が2番に勝つかを、矛盾する2つを同時に渡して確かめます。

FORCE_PROMPT_CACHING_5M=1 CLAUDE_CODE_PROMPT_CACHE_TTL=1h \
  claude -p "hello" --output-format json | jq '.[-1].usage.cache_creation'
{
  "ephemeral_1h_input_tokens": 0,
  "ephemeral_5m_input_tokens": 14561
}

1h を明示しても5分で書かれました。組織の managed settings で長いTTLを敷かれている環境でも、手元に FORCE_PROMPT_CACHING_5M=1 を置けば5分へ落とせます。公式が挙げている用途も、キャッシュ挙動のデバッグと managed settings の上書きの2つです。

4番の experimental.cacheTtl はサブエージェントの frontmatter に書きます。サブエージェント単位でTTLを変えられますが、Claudeサブスクが usage credits を使っている間は 1h の指定が黙って無視されます。

壊す操作と保つ操作の境目

作り直しが起きるかどうかは、その操作がプレフィックスの中を触るか、末尾に足すだけかで決まります。

操作キャッシュ補足
/model でモデル切替壊れる/effort の変更も同じ扱い
fast mode をON壊れる1会話につき1回だけ。OFFにして戻す往復は壊れない
MCPサーバーの接続・切断条件次第tool search で遅延ロードされていれば壊れない
Bash などツール名を丸ごとdeny壊れるBash(rm *) のようなスコープ付きは壊れない
/compact壊れる会話層を要約で置き換える設計上そうなる
Claude Codeのアップグレード壊れる長いセッションを再開するときが最も高い
CLAUDE.mdの編集保つただし編集内容もセッション中は反映されない
output styleの変更保つ同じく次の /clear か再起動まで効かない
permission modeの切替保つopusplan の plan mode だけ例外
/rewind保つ戻り先のプレフィックスは既にキャッシュ済み
スキル・コマンドの呼び出し保つ指示を末尾にユーザーメッセージとして足すだけ

MCPサーバーの行が条件次第なのは、tool search が効いているとツール定義がキャッシュ済みプレフィックスの外に置かれるためです。ツール定義を遅延ロードさせる設定判断は Claude Code MCP tool search—実測7万トークン削減の設定判断 で測りました。

rewindとcompactは履歴を減らす向きが逆

どちらも履歴を短くする操作ですが、キャッシュから見ると正反対です。/rewind は過去のプレフィックスまで切り戻すだけなので、その時点のキャッシュエントリをそのまま読めます。以降のターンが全部そのプレフィックスを経由して読んでいるため、元のターンがTTLより昔でもエントリは温まったまま。

対して /compact は要約という新しい履歴を作るので、共有するプレフィックスがありません。要約を生成するリクエスト自体はキャッシュが温かいうちなら会話のプレフィックスを読めるので、コンテキストサイズから想像するより安く済みます。ただし長い休止の後に再開してから /compact を打つと、キャッシュが切れているぶん全履歴を uncached で処理します。捨てたい経路があるなら /compact ではなく /rewind

cwdが変わると読める量が初回に戻る

“Cache scope” セクションによると、システムプロンプトには作業ディレクトリ、プラットフォーム、シェル、OSバージョン、auto memory のパスが埋め込まれています。ディレクトリが違えばプレフィックスも違う。同じリポジトリの worktree どうしでもキャッシュは共有されません。

/tmp で3回叩いて温めた状態と、初めて入る /tmp/cachescope を、同じ FORCE_PROMPT_CACHING_5M=1 で比較しました。

作業ディレクトリcache_readcache_creation
/tmp(3回目)13,01014,561
/tmp/cachescope(初回)10,04717,540

読めた量が 2,963 トークン減り、書き直しが 2,979 トークン増えました。この 10,047 という値は /tmp で初回に実行したときの cache_read と完全に同じです。ディレクトリを跨いでも読める共通部分が約1万トークンあり、その先はディレクトリごとに別のキャッシュになっている、と読めます。

worktree を切って並列開発する運用なら、worktree の数だけ書き込みが立つ計算です。逆に同じディレクトリで並列に立てたセッションどうしは、プレフィックスが揃うのでお互いのキャッシュを読みます。

ヒット率は/usageとJSON出力で見る

セッション単位なら/usage

v2.1.251以降、main conversation の最初の応答が返った後に /usage の Session ブロックへ Prompt cache (main) の行が出ます。載るのはそのセッションのヒット率、ミス回数、今キャッシュが温かいかどうか。statusline スクリプトからは prompt_cache オブジェクトで同じ値を読めます。

1リクエスト単位ならcache_creationを直接見る

どちらのTTLで書かれたかまで確認するなら JSON 出力が確実です。

claude -p "hello" --output-format json \
  | jq '.[-1].usage | {cache_creation, cache_read_input_tokens}'
{
  "cache_creation": {
    "ephemeral_1h_input_tokens": 16065,
    "ephemeral_5m_input_tokens": 0
  },
  "cache_read_input_tokens": 9922
}

cache_creation_input_tokens より cache_read_input_tokens が大きいターンが続いていれば効いています。ターンをまたいで書き込みが高止まりしているなら、プレフィックスの中で毎回何かが変わっている。上の表の「壊れる」側に心当たりを探すことになります。

組織単位で追うなら OpenTelemetry exporter が、キャッシュの読み込みトークンと書き込みトークンをユーザー別・セッション別に出します。

まとめ

コストの読み方から。

  • claude -p "hello" 1回の $0.174971 は、1時間TTL書き込み 16,065トークン($0.160650)、読み込み 9,922トークン($0.004961)、出力 374トークン($0.009350)の合計と小数第6位まで一致する
  • 単価倍率は5分書き込みが1.25倍、1時間書き込みが2倍、読み込みはどちらも0.1倍
  • --output-format json は配列を返すので、jq '.[-1].usage' で取る

設定と操作の側も整理しておきます。

  • サブスクのプラン内なら main conversation は1時間、サブエージェント側は5分。使用量クレジットに入ると main も5分へ落ちる
  • TTLの指定は promptCacheTtlsubagentPromptCacheTtl(v2.1.242以降)。FORCE_PROMPT_CACHING_5M=1 がすべてに優先する
  • /model/effort・fast modeのON・/compact・アップグレードはプレフィックスを作り直す。/rewind と CLAUDE.md の編集は作り直さない
  • 作業ディレクトリがシステムプロンプトに埋まっているため、worktree ごとにキャッシュは別になる
よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!
目次