Claude CodeにCache-Controlを点検させる — curl -Iだけ許可する手順
curl -Iだけを許可したClaude Codeに応答ヘッダを集めさせ、no-storeの付けすぎ・max-ageの短さ・圧縮の欠落を洗う手順。ETagと304の確認まで。
Cache-ControlとETagの見直しは、設定を直す前の「いまの応答を集める」作業が大半です。この収集だけをClaude Codeに任せるなら、許可するコマンドは curl -I に絞れます。ヘッダだけを取る読み取り専用の操作なので、権限の範囲を小さく保ったまま、静的資産とAPIの応答を一覧にできます。
この記事の手順は3段です。curl -I だけを許可する設定を置き、集めたヘッダから no-store の付けすぎ・max-age の短さ・圧縮の欠落を洗い、ETagで304が返るかまで確かめます。
Claude APIの cache_control(プロンプトキャッシュ)とは別物です。そちらはツール定義のcache_controlはどこに置くかにあります。Claude Code自身の通信が圧縮でつまずく件はClaude Codeのgzip圧縮がプロキシで壊れるときの対処法の領分です。ここで扱うのは、あなたが作っているWebサイトやAPIが返すHTTPキャッシュの話です。
curl -Iだけを許可する設定
HTTPヘッダの収集は、権限ルールに Bash(curl -I *) を1本置くだけで始められます。* は空白を含む任意の文字列に一致し、末尾の * の前に空白を置くと、引数なしの curl -I にも一致します。
{
"permissions": {
"allow": [
"Bash(curl -I *)",
"Bash(curl -sI *)"
]
}
}2本書くのは、ルールがコマンドの文面で照合されるためです。curl -I https://... と curl -sI https://... は別の文字列なので、後者は前者のルールに一致しません。-s(進捗表示を消す)を付けて呼ばせたいなら、後者も足します。
Bash権限の組み込みの読み取り専用コマンドには ls cat grep head などが入っていますが、curl は一覧にありません。ルールを置かなければ、curl -I も毎回確認が出ます。
CLAUDE.mdに調べ方の型を書く
権限は「できること」を決めるだけで、何を調べるかは決めません。調べ方はCLAUDE.mdかプロンプトに型として書いておくと、毎回ぶれません。
## HTTPキャッシュの点検
- 応答ヘッダの取得は `curl -sI -H 'Accept-Encoding: gzip, br' <URL>` だけを使う
- 見るのは Cache-Control / ETag / Last-Modified / Content-Encoding / Vary / Age
- URLごとに「静的資産 / HTML / API」のどれかを先に決める
- 設定ファイルを直す前に、集めたヘッダを表にして見せるAccept-Encoding を付けるのには理由があり、後の節で実際の出力を見せます。
許可ルールが通す範囲と通さない範囲
本記事の例は curl -sI … | grep のようなパイプ形ですが、複合コマンドはサブコマンドごとに照合され、grep や head は読み取り専用として通ります。そのため、許可ルールを増やさずに済みます。
Bash(curl -I *) は「curl -I で始まる文字列」を通します。* は任意のテキストに一致するので、-I のあとに何が続いても、ルールの上では通ります。ヘッダ取得に絞ったつもりでも、ルールが保証しているのは冒頭の curl -I だけです。
落とし穴
curl -Iの許可は、curlの使い方全般の制限にはならない
権限ルールはClaudeが書いたコマンド文面と照合されます。Bash(curl *) を拒否していても /usr/bin/curl や sh -c 'curl ...' は止まらず、逆に許可ルールも文面が変われば一致しません。通信先を実際に制限したいときは、サンドボックスの network.allowedDomains で通信先を絞ります。
拒否ルールとの関係も押さえておきます。ルールは拒否、確認、許可の順に評価され、最初に一致したものが結果を決めます。広い Bash(curl *) を拒否に置いたまま Bash(curl -I *) を許可に足しても、許可は効きません。curl を丸ごと拒否している環境なら、拒否側を外すか、許可の置き場所を見直す必要があります。
通信先は、サンドボックスの許可ドメインで絞れます。ドキュメントの例は次の形です。点検したいホストだけを入れておけば、curl -I が別のホストに向かう事故は避けられます。
{
"sandbox": {
"enabled": true,
"network": {
"allowedDomains": ["example.com", "*.example.com"]
}
}
}サンドボックスを有効にすると、許可リストにないホストへの最初の通信は承認を求められます。点検の前に、対象ホストを入れておくと流れが止まりません。
HEADで取れるヘッダの読み方
curl -I は、マニュアルによればヘッダだけを取得するオプションで、HTTPではHEADリクエストを使います。本文は転送されないので、画像やバンドルのような大きな資産でも安全に回せます。
見るヘッダは6つあれば足ります。
| ヘッダ | 見ること |
|---|---|
| Cache-Control | 見ることmax-age の長さ、no-store / no-cache / private / immutable の有無 |
| ETag | 見ること値があるか(再検証で304を返す前提になる) |
| Last-Modified | 見ることETagとあわせて返っているか |
| Content-Encoding | 見ることgzip / br など圧縮されているか |
| Vary | 見ることAccept-Encoding が入っているか |
| Age | 見ること共有キャッシュ(CDN)から返った経過秒数 |
Accept-Encodingを付けないと圧縮が見えない
圧縮の有無は、リクエストに Accept-Encoding を付けるかどうかで見え方が変わります。サーバーの応答は、Accept-Encoding などのリクエストヘッダに依存することがあります。実際に、同じURLを2通りで取って比べました(curl 8.7.1、2026年10月28日に取得、example.com のトップページ)。
curl -sI https://example.com/ | grep -iE "^(HTTP|content-encoding)"
curl -sI -H 'Accept-Encoding: gzip, br' https://example.com/ \
| grep -iE "^(HTTP|content-encoding)"1本目は HTTP/2 200 だけが出て、content-encoding の行はありませんでした。2本目では content-encoding: br が返りました。同じサーバーでも、圧縮を受け入れると伝えない限りは圧縮の欠落に見えます。
この結果は、そのホストをその時点で取ったときの観察です。サーバーやCDNが違えば挙動は変わるので、一般則としてではなく「ヘッダを付けて比べる理由」として使ってください。CLAUDE.mdの型に Accept-Encoding を入れたのも、この取り違えを避けるためです。
curlのマニュアルには --compressed というオプションもあります。こちらは圧縮応答を要求して自動で展開するもので、保存したヘッダは変更されないため、あとで読み返すと圧縮されたままのように見えることがあります。ヘッダの点検には -H 'Accept-Encoding: ...' を明示するほうが、何を頼んだかが記録に残ります。
静的資産・HTML・APIで置き方を変える
点検の物差しは、URLの種類ごとに違います。ほとんどのサイトは、次の2つのパターンの組み合わせで覆えます。
URLの種類で変わるCache-Controlの置き方
ファイル名にハッシュやバージョンが入る資産
bundle.v123.js のようにURLが内容と対応する資産です。Cache-Control: public, max-age=31536000, immutable を付け、Last-Modified と ETag も返します。
URLを変えられないHTML・favicon・API
HTMLはURLを装飾できないので、no-cache を基本にします。no-cache は保存を許し、再利用前に再検証を求める指定です。ユーザーごとに内容が変わるなら private も足します。
public は、Authorization ヘッダ付きの応答も保存できるようにする指定です。認証つきの応答を返すAPIの資産に付ける前に、本当に必要かを確かめてください。長さに迷うときは、max-age=2592000(1か月)にとどめる選択肢があります。
APIは、URLを変えられないという意味でHTMLと同じ側に入ります。個人ごとに内容が変わる応答は private が前提です。付け忘れると共有キャッシュに保存され、別のユーザーに再利用されることがあります。
点検で拾う3つの指摘
集めたヘッダは、Claudeに表へ整形させてから判断します。拾う指摘は、次の3つに絞ると迷いません。
no-storeの付けすぎ
no-store は、応答をどのキャッシュにも保存させない指定です。MDNは、これを気軽に付けるのは勧めないとしています。ブラウザの戻る・進むのキャッシュ(bfcache)など、ブラウザが持つ利点が失われるためです。
「常に最新を返したい」だけなら、no-cache で足ります。保存はしても、使う前に毎回サーバーへ確かめに来ます。ETagがあれば、変わっていなければ304で本文なしの返事になります。
見かけるのが no-store, no-cache, max-age=0, must-revalidate, proxy-revalidate のような全部盛りです。MDNによれば、これは古いプロキシキャッシュが no-store を理解しない時代の回避策で、現在は no-cache で置き換えるよう勧められています。HTTPSが普及して、途中のプロキシは通信を中継するだけで保存できないことが多いためです。
また、no-store は、すでに保存された同じURLの古い応答を消しません。直したつもりでも、古い応答が再利用されることがある点は覚えておいてください。
max-ageの短さと長すぎ
ハッシュ付きの資産に max-age=60 のような短い値が付いていないかを見ます。内容がURLで決まる資産は、長く持たせても古い内容が出ません。
逆の失敗もあります。長い max-age で保存された応答は、途中のサーバーからは消せません。Clear-Site-Data: cache で消せるのはブラウザのキャッシュだけで、中間キャッシュには効きません。頻繁に更新するURLに長い max-age を付けないことが、この点検の裏側のチェックです。
圧縮の欠落とVary
Content-Encoding が出ないテキスト資産(HTML・CSS・JS・JSON)を拾います。ただし前の節のとおり、Accept-Encoding を付けて取り直してからの判定です。
圧縮を出し分けるサーバーなら、Vary: Accept-Encoding が入っているかも確認してください。Varyは、同じURLでもリクエストヘッダで内容が変わる応答を、別々に保存するための仕組みです。
一括で洗い、直してから再検査する
URLが数十本あるなら、1本ずつ頼むより、スクリプトにまとめて呼ばせるほうが速く済みます。
#!/bin/sh
# scripts/headers.sh — URL一覧の応答ヘッダを集める
while read -r url; do
echo "== $url"
curl -sI -H 'Accept-Encoding: gzip, br' "$url" \
| grep -iE "^(HTTP|cache-control|etag|last-modified|content-encoding|vary|age)"
done < "${1:-urls.txt}"ただし、このスクリプトを許可するルールは Bash(scripts/headers.sh *) になります。権限が見るのは入力されたコマンドの文面なので、中でcurlが何を呼んでいるかは、このルールの外です。スクリプトの中身はレビューしたうえで、読み取りだけに保ってください。
Claudeに渡す指示は、事実の収集と判断を分けると安定します。
urls.txt の各URLについて scripts/headers.sh の出力を取り、
URL / 種類(静的資産・HTML・API)/ Cache-Control / ETag / 圧縮 の表にして。
そのあと、no-store・短いmax-age・圧縮欠落に当たる行だけ、直し方を1行ずつ添えて。
設定ファイルはまだ編集しないで。表を見て方針が決まったら、設定を直します。直したあとの再検査では、ETagで304が返るかを見ます。
curl -sI https://example.com/app.css | grep -iE "^(etag|cache-control)"
curl -sI -H 'If-None-Match: "取得したETagの値"' https://example.com/app.css \
| head -12本目の先頭行が HTTP/2 304 なら、ETagによる再検証が働いています。サーバーはリクエストの If-None-Match と手元のETagが同じなら304を返し、違えば新しい内容で200を返します。HEADで304まで確認できるかはサーバーの実装次第なので、200が返ったときは設定の誤りと決めつけず、GETでも確かめてください。
同時に確かめたいこと
- ハッシュ付きの資産に
immutableを付けると、再読み込みでの不要な再検証を避けられます。Chromeはimmutableに頼らず、サブリソースの再読み込み時に再検証しない実装へ変わっています。ブラウザによる差があるので、効果を過信しないでください。 Expiresは、Cache-Control: max-ageと両方ある場合はmax-ageが優先されます。新規に付ける必要はありません。- 何も指定しない応答は「キャッシュしない」ではなく、ヒューリスティックキャッシュで再利用されます。MDNは、すべての応答に明示的な
Cache-Controlを付けるよう勧めています。
点検の結果を設定に落とす順番
最後に、直す順番です。影響が大きく、戻しやすいものから進めます。
- 全部盛りの
no-storeをno-cacheに置き換える(HTMLとAPI) - ハッシュ付き資産に長い
max-ageとimmutableを付ける - 個人ごとに変わる応答に
privateが付いているか確かめる - 圧縮の欠落を直す(Webサーバー側、またはCDN側の設定)
Cache-Controlの値を変えたあとは、すでに配った応答が期限まで残ります。長い max-age を付ける前には、URLにハッシュを入れる仕組み(ビルド設定)が先に動いていることを確認してください。バンドルの分割や上限の置き方はClaude Codeでバンドルサイズを削減する、旧APIへの廃止予告をヘッダで出す方法はSunsetヘッダで旧APIを廃止予告する手順にあります。curlで部分HTMLを確かめる流れはClaude Codeでhtmxアプリを作ると併せて使えます。
まとめ
curl -I だけを許可しておけば、Claude Codeは応答ヘッダの収集役になれます。ただしルールが保証するのは冒頭の文面だけなので、通信先の制限はサンドボックスの許可ドメインで別に置きます。
点検の勘所は、no-store を no-cache に直せる箇所、ハッシュ付き資産の max-age、Accept-Encoding を付けて見た圧縮の3点です。直したあとは、ETagの If-None-Match で304が返るかまで確かめて終わりにしてください。
参照した一次ソース
この記事の記述は、次の一次ソースと照合しています。更新時に参照先が変わった場合は一覧も更新します。