Claude Codeの--resumeでキャッシュが効かず、コストが20倍になった問題
--print --resumeでキャッシュが再利用されずコストが20倍になった回帰の原因と、v2.1.90で入った修正、症状の確認方法をまとめます。
claude --print --resumeでセッションを再開すると、会話が長くなってもプロンプトキャッシュが一切再利用されない回帰が2026年3月に報告されました。原因は再開時にmessages[0]の中身が新規セッションと食い違い、キャッシュのプレフィックス一致が壊れることでした。公式の修正はv2.1.90で入っています。
--resumeで何が起きたか — cache_readが伸びずコストが20倍に
--print --resumeは、既存セッションを非対話(ヘッドレス)で再開し、1メッセージだけ送って終了するコマンドです。Discord botやCI連携など、外部システムからClaude Codeを1回のプロセス起動として呼び出す用途で使われます。
GitHub issue #34629の報告者は、Ubuntu上でDiscord bot(claude --print --model <model> --resume <session-id> --output-format stream-json --verboseをstdin経由のプロンプトで呼ぶ構成)を運用していました。v2.1.68では正常だったキャッシュ挙動が、v2.1.69以降のバージョンで壊れていることを、同一セッションに3通以上のメッセージを送る検証で確認しています。
正常時(v2.1.68)と回帰後(v2.1.76)で、同じセッションを送り続けたときの定常状態を比較すると次のとおりです。
| バージョン | cache_read(定常) | cache_create(定常) | 1メッセージあたりの概算コスト |
|---|---|---|---|
| v2.1.68(正常) | cache_read(定常)37,295 | cache_create(定常)802 | 1メッセージあたりの概算コスト約$0.02 |
| v2.1.76(回帰後) | cache_read(定常)14,569 | cache_create(定常)55,954 | 1メッセージあたりの概算コスト約$0.36 |
issue本文は「約20倍のコスト増」と表現しており、上の定常状態の数値だけでも0.36ドルは0.02ドルの18倍です。v2.1.76のcache_readはClaude Codeのシステムプロンプト分(約14.5kトークン)で頭打ちになり、会話ターンはメッセージのたびにcache_createとして丸ごと課金されていました。
報告者はモデルやコンテキスト長を変えたテストも行っており、退行がモデル非依存でバージョン依存だと確認しています。
| バージョン | モデル | コンテキスト | cache_readは伸びるか | 定常コスト/メッセージ |
|---|---|---|---|---|
| v2.1.68 | モデルopus | コンテキスト200k | cache_readは伸びるか伸びる | 定常コスト/メッセージ約$0.02 |
| v2.1.68 | モデルopus[1m] | コンテキスト1M | cache_readは伸びるか伸びる | 定常コスト/メッセージ約$0.02 |
| v2.1.76 | モデルopus | コンテキスト200k | cache_readは伸びるか14.5kで固定 | 定常コスト/メッセージ$0.04〜$0.40(増加) |
| v2.1.76 | モデルopus[1m] | コンテキスト1M | cache_readは伸びるか14.5kで固定 | 定常コスト/メッセージ$0.35〜$0.40 |
| v2.1.76 | モデルopus-4-5(20251101) | コンテキスト200k | cache_readは伸びるか14.5kで固定 | 定常コスト/メッセージ$0.04〜$0.40(増加) |
なぜキャッシュが壊れたか — messages[0]の非対称性
キャッシュはAPIリクエストのmessages配列の先頭が前回と一致する範囲で再利用されます。この回帰では、新規セッションと再開セッションとでmessages[0]の組み立て方そのものが異なっていました。
issueのコメントでユーザーjmarianski氏が、MITMプロキシで複数バージョンのAPIリクエストを実際にキャプチャして原因を特定しています。新規セッションでは、デフォルトツールやMCP・skillsの案内といったシステムリマインダーが最初のメッセージ(messages[0])にまとめて入り、合計で約13.4KBになります。一方、再開セッションではmessages[0]に現在日時などの最小限の情報(約352B)しか入らず、同じ案内は会話の末尾(messages[N])に追記される構造でした。
| 比較項目 | 新規セッション | 再開セッション |
|---|---|---|
messages[0]の内容 | 新規セッション案内4種+ユーザー入力(約13.4KB) | 再開セッション最小限の1種のみ(約352B) |
| キャッシュ課金用のバージョンハッシュ | 新規セッションmessages[0]から算出 | 再開セッション内容が違うため別の値に |
キャッシュ境界(cache_control)の位置 | 新規セッションmessages[0]末尾 | 再開セッションmessages[N]末尾 |
3つの差はどれもキャッシュのプレフィックス一致を壊しますが、根本原因はmessages[0]の内容そのものが違う点だと分析されています。この案内群を持つdeferred_tools_deltaという添付タイプがv2.1.69で新たに導入されており、v2.1.68のコードには存在しないことも確認されています。v2.1.68では新規・再開のどちらもmessages[0]が最小限の情報だけで一致していたため、この非対称性自体が発生していませんでした。
分析コメントでは、修正の方向性として複数の案が挙げられていました。再開時にも案内群をmessages[0]と同じ位置へ注入する案、案内群をメッセージではなくシステムプロンプト側のsystem[]パラメータへ移す案、課金用ハッシュの算出時にシステムリマインダーを除外する案などです。実際にv2.1.90で採用された修正がこれらのどれに近いかはchangelogから読み取れませんが、対象範囲(--resume・deferred tools・MCPサーバー・カスタムagent)は一致しています。
--print --resumeを毎回新規プロセスから呼ぶ運用が最も影響を受けます。前回呼び出しのキャッシュを同一プロセス内で使い回せないため、再開のたびにこの食い違いを踏み、会話が伸びるほど再送コストが積み上がる構造でした。
コメント欄の分析では、遷移パターンごとにキャッシュの当たり方が整理されています。
| 遷移パターン | キャッシュ | 理由 |
|---|---|---|
| 新規セッションの1ターン目→2ターン目 | キャッシュHIT | 理由同一プロセス内でmessages[0]が変わらない |
| 新規セッション→再開(別プロセス) | キャッシュMISS | 理由messages[0]が13.4KBから352Bへ激変する |
| 再開後の1ターン目→2ターン目(同一プロセス) | キャッシュHIT | 理由再開直後にmessages[0]が確定すれば、その後は安定する |
再開→再開(--printのように毎回新規プロセス) | キャッシュ部分的なHIT | 理由システムプロンプト分(約11k)だけ当たり、会話本体は毎回cache_create |
--print --resumeのユースケースが最も割を食うのは、この最後の行のとおり呼び出しのたびに新規プロセスが起動し、直前の再開で作られたキャッシュを引き継げないためです。会話が伸びるほど再送分も比例して増えるため、長寿命のbotセッションほど累積コストへの影響は大きくなります。
プロンプトキャッシュそのものの基本的な仕組みと課金の考え方は、Claudeのプロンプトキャッシュの仕組みで解説しています。
自分の環境で症状を確認する方法
症状の確認は、stream-json形式の出力に含まれるcache_read_input_tokensとcache_creation_input_tokensを見るのが最も直接的です。
claude --print --resume <session-id> --output-format stream-json --verbose同じセッションIDに3通以上のメッセージを送り、result出力内のcache_read_input_tokensがメッセージを重ねても増えず、cache_creation_input_tokensだけが会話の長さに比例して伸びていれば、この回帰と同じ症状です。
v2.1.260以降では、/costコマンドとステータスラインのprompt_cacheフィールドに「キャッシュミスの推定原因」が表示されるようになっており、ツール定義の変化やTTL経過とあわせて確認できます。ステータスラインでの具体的な表示方法はstatuslineでprompt_cacheを表示するにまとめています。
通常のキャッシュ挙動と見分けるポイントは、1メッセージ目のコールドスタートで終わるか、2メッセージ目以降も続くかです。どのバージョンでも新規セッションの最初の1通はcache_createが発生し、通常単価に近いコストがかかります。ここまでは仕様どおりの挙動です。問題は2通目以降で、正常ならcache_readが会話量に応じて伸び続けるのに対し、この回帰ではcache_readがシステムプロンプト分のまま動かなくなります。3通以上のやり取りでcache_readが一度も増えなければ、単なるTTL切れではなくこの回帰と同じ症状だと判断できます。
利用形態別の影響 — 新規プロセスでの再開ほど深刻
同じ--resumeでも、プロセスの起動パターンによって影響の大きさは変わります。issueのコメントで示された挙動の整理を、利用形態に当てはめると次のとおりです。
| 利用形態 | 影響 | 理由 |
|---|---|---|
ターミナルで--resumeして同一プロセス内で対話を続ける | 影響限定的(再開直後の1ターンのみ) | 理由再開直後だけmessages[0]が変わり、以降は同一プロセス内で安定してキャッシュが効く |
claude --print --resumeを毎回新規プロセスから呼ぶ自動化(bot・CI等) | 影響大きい | 理由呼び出しごとに前回のキャッシュを使い回せず、非対称性を毎回踏む |
アイドル時間が長く、セッションを空けてから--resumeする場合 | 影響キャッシュのTTL切れとも重なりうる | 理由TTL経過による通常のキャッシュ切れと、この回帰の影響を切り分けにくい |
セッションをアイドルのまま長時間放置した場合との切り分けや、TTL設計そのものの考え方はプロンプトキャッシュはClaude Codeの利用上限をどう軽くするかで扱っています。
修正はいつ入ったか
v2.1.69でのdeferred_tools_delta導入から、v2.1.90での公式修正までの流れは次のとおりです。
| 時期 | 出来事 |
|---|---|
| 〜v2.1.68 | 出来事正常。会話が伸びるほどcache_readが増える |
| v2.1.69 | 出来事deferred_tools_delta導入。新規・再開セッションでmessages[0]の内容が食い違い始める |
| 2026-03-15 | 出来事issue #34629が報告される |
| 2026-03-30 | 出来事コミュニティがAPIリクエストのキャプチャでmessages[0]の非対称性を特定 |
| v2.1.90 | 出来事公式changelogに--resumeの修正が記載され、issueがクローズ |
v2.1.90のchangelogには「Fixed --resume causing a full prompt-cache miss on the first request for users with deferred tools, MCP servers, or custom agents (regression since v2.1.69)」と明記されています。対象範囲(deferred tools・MCPサーバー・カスタムagent利用時)は、コミュニティが特定した原因と一致します。
同じ2026年3〜5月ごろには、1時間TTLが通知なく5分へ切り替わる別の回帰も見つかっています。原因も修正版も異なる独立した不具合で、詳細はClaude CodeのキャッシュTTLが1時間から5分に短縮された問題と対処法にまとめました。
修正後に残っていた注意点
v2.1.90のリリース直後、issueのコメント欄では「会話の最初の1回の再開だけは、まだキャッシュを作り直すことがある」という検証結果も報告されていました。修正が主要な部分に効いていることは確認されつつ、細部の挙動確認が続いていたことになります。issue自体はv2.1.90のリリースを理由にクローズされており、それ以降のchangelogにこの回帰が再発したという記載は見当たりません。
修正が公式に届くまでの間、issueのコメント欄では複数のユーザーがcli.jsを書き換える非公式パッチや、リクエストをフックしてmessages[0]を手動で組み直すNode.jsスクリプトを公開していました。バージョン間で内部の変数名が変わるため、こうした非公式パッチは対象バージョンが変わるたびに作り直しが必要という制約付きでした。
同じコメント欄では、修正前の暫定策として「--resumeのセッションを短く保ち、頻繁に新しいセッションIDへ切り替える」対処も挙げられていました。会話が伸びるほどcache_create分の再送コストが積み上がる構造だったため、セッションを短く区切ることで1回あたりの無駄を抑えるという考え方です。ただし、これは会話の連続性を犠牲にする代わりの応急対応であり、v2.1.90で修正が入った後は不要な工夫です。
まとめ
--print --resumeのキャッシュ退行は、v2.1.69からv2.1.89までのバージョンに影響し、v2.1.90の公式修正で解消しています。ヘッドレス自動化でClaude Codeを呼んでいる場合、まず現在のバージョンがv2.1.90以降かを確認するのが最初の一手です。何らかの理由で古いバージョンを固定運用している場合は、stream-json出力のcache_read_input_tokensがメッセージを重ねても増えないかを確認してください。/costやステータスラインのprompt_cache表示でも、キャッシュミスの推定原因を日常的に追えます。