Claude CodeでCloudflare Workersを開発する — wranglerの権限設計
wrangler devは通し、本番のdeployやsecret putは止める。Claude Codeのpermissions(allow・ask・deny)とフックで、Workers開発の権限を切り分ける設定例をまとめます。
Claude CodeにCloudflare Workersを開発させるとき、最初に決めるのは「どのwranglerコマンドまで自動で通すか」です。wrangler devは何度走っても困りません。一方でwrangler deployは本番を書き換え、wrangler deleteはWorkerと関連リソースを消します。
結論から書くと、切り分けは3段になります。ローカルで完結するものはallow、本番に届くものはask、消すものと迂回口になるものはdenyです。ただしaskとallowは「具体的なほうが勝つ」ルールではありません。書き方を間違えると、通したいコマンドが止まり、止めたいコマンドが通ります。この記事はその落とし穴を避けた設定例です。
wranglerコマンドを3段に分ける
まず、コマンドごとに「何に届くか」を並べます。Cloudflareのコマンド一覧にある説明が根拠です。
| コマンド | 何をするか | 段階 |
|---|---|---|
wrangler dev --local | 何をするかローカルサーバーで開発。リモートのbindingは無効 | 段階allow |
wrangler types | 何をするかWorker設定から型を生成 | 段階allow |
wrangler deploy --dry-run | 何をするかコンパイルだけ行い、デプロイしない | 段階allow(スクリプト経由。下の設計参照) |
wrangler versions upload | 何をするか新バージョンを作る。すぐには反映しない | 段階allow候補 |
wrangler versions list / deployments list | 何をするかバージョンとデプロイ履歴の表示 | 段階allow |
wrangler versions secret put | 何をするかシークレットの新バージョンを作る。デプロイはしない | 段階allow候補 |
wrangler preview | 何をするかブランチやPR用のPreviewデプロイを作る。本番はdeploy | 段階ask |
wrangler dev --remote | 何をするかCloudflare上のリモートリソースとデータを使う | 段階ask |
wrangler triggers deploy | 何をするかversions uploadで作った版のRoute・Cron Triggerの変更を反映(experimental) | 段階ask |
wrangler secret put / bulk | 何をするかシークレット更新。新バージョンを作って即デプロイ | 段階ask |
wrangler versions deploy | 何をするか作成済みバージョンを反映 | 段階ask |
wrangler rollback | 何をするか指定バージョンを全ルートで即座に有効化 | 段階ask |
wrangler deploy | 何をするかWorkerを本番へデプロイ | 段階deny(直接叩く形。下の設計参照) |
wrangler delete | 何をするかWorkerと関連リソースをすべて削除 | 段階deny |
見落としやすいのはsecret putです。公式の説明では、このコマンドはWorkerの新しいバージョンを作り、そのまま即座にデプロイします。名前は「設定」ですが、本番反映の性質はdeployと同じです。
同じ名前でもversions secret putは別物で、新しいバージョンを作るだけです。ルールのBash(npx wrangler secret *)はnpx wrangler versions secret putにマッチしません。バージョンだけ作らせて反映は人がversions deployで行う運用なら、後者をallow側に置けます。
wrangler previewにはシークレット用のサブコマンド(preview secret putなど)もあります。wrangler secretのaskにはマッチしないので、後の設定例はBash(npx wrangler preview *)をaskに置きます。wrangler previewはPreviewデプロイを作るコマンドで、本番にはdeployを使うと書き分けられています。本番には触れませんが、Cloudflare上に実体ができるため、dev --localと違ってaskに置きます。
見落としやすいもう1つがtriggers deployです。versions uploadで作った版に対して、RouteやCron Triggerの変更を反映するコマンドで、deployのdenyにもversions deployのaskにもマッチしません。
wrangler devは既定でローカル実行ですが、--localを付けるとリモートbindingがすべて無効になり、remote: falseと同じ挙動になります。設定ファイル側でbindingをremoteにしていても、--localがあれば本番のD1やKVには触れません。Claudeに書かせるコマンドは--local付きに寄せると、本番のD1やKVに触れる余地が消えます。
settings.jsonの設定例
前提として、ルールはdeny、ask、allowの順に評価され、最初に当たったものが結果になります。具体性は順序を変えません。広いdenyは、より狭いallowに当たるコマンドも止めます。同じ関係がaskとallowの間にもあります。
この性質から、allowで穴を開けたいコマンドはaskやdenyのパターンと重ならないように書く必要があります。次の例は、deployを直接叩く形をdenyにし、承認付きの本番デプロイだけをnpm runのスクリプト経由に一本化します。
{
"permissions": {
"allow": [
"Bash(npx wrangler dev --local *)",
"Bash(npx wrangler types *)",
"Bash(npx wrangler whoami)",
"Bash(npx wrangler versions list *)",
"Bash(npx wrangler deployments list *)",
"Bash(npx wrangler check startup)",
"Bash(npm run check:deploy)",
"Bash(npm run deploy:staging)"
],
"ask": [
"Bash(npx wrangler dev --remote *)",
"Bash(npx wrangler secret *)",
"Bash(npx wrangler preview *)",
"Bash(npx wrangler versions deploy *)",
"Bash(npx wrangler triggers deploy *)",
"Bash(npx wrangler rollback *)",
"Bash(npm run deploy:production)",
"Edit(/wrangler.jsonc)",
"Edit(/package.json)"
],
"deny": [
"Bash(npx wrangler deploy *)",
"Bash(wrangler deploy *)",
"Bash(npx wrangler delete *)",
"Bash(wrangler delete *)",
"Read(./.dev.vars*)",
"Read(./.env*)"
]
}
}Bash(npx wrangler deploy *)をdenyに置いたので、--dry-run付きのdeployも同じルールで止まります。wrangler deploy --secrets-fileのようにデプロイと同時にシークレットを渡す経路も、同じdenyで止まります。denyから例外は切り出せません。ドライランはpackage.jsonのスクリプトに包みます。
{
"scripts": {
"dev": "wrangler dev --local",
"check:deploy": "wrangler deploy --dry-run --outdir dist",
"deploy:staging": "wrangler deploy --env staging",
"deploy:production": "wrangler deploy"
}
}権限ルールが照合するのは、Claudeが書いたコマンド文字列です。npm run check:deployの中で何が走るかは見ません。だからスクリプト名が「境界」になり、check:deployはallow、deploy:productionは毎回askという分け方が成立します。
--env stagingは設定ファイルの[env.staging]のような環境ごとの定義を選ぶフラグです。公式の例にもnpx wrangler deploy --env stagingが載っています。なおwrangler.jsoncはCloudflareが新規プロジェクトに勧める設定ファイルで、TOMLのwrangler.tomlも使えます。
ルールをすり抜ける書き方と、その塞ぎ方
denyやaskのルールは、Claudeが普段書く形を止めるものです。プログラムそのものの禁止ではありません。公式ドキュメントも、Bash(git push *)がgit -C . pushを止めない例を挙げています。wranglerでも同じ事情が起こります。
| 書き方 | Bash(npx wrangler deploy *)で止まるか |
|---|---|
npx wrangler deploy --env staging | Bash(npx wrangler deploy *)で止まるか止まる |
cd worker && npx wrangler deploy | Bash(npx wrangler deploy *)で止まるか止まる(&&で分割し、各コマンドを別々に照合) |
pnpm wrangler deploy | Bash(npx wrangler deploy *)で止まるか止まらない(別のルールが要る) |
bash -c 'npx wrangler deploy' | Bash(npx wrangler deploy *)で止まるか止まらない |
npm run deploy:production | Bash(npx wrangler deploy *)で止まるか止まらない(別ルールのaskで拾う) |
上の設定例がnpx wranglerと素のwranglerの両方を書いているのは、この理由です。pnpmやyarnを使うプロジェクトなら同じ形で追記します。
npxは、Claude Codeが自動で取り除くラッパーの一覧に入っていません。公式はdevbox runなどの環境ランナーについて、Bash(devbox run *)はrun以降の何でも通すと警告しています。npx wrangler ...のallowは、dev --localのようにサブコマンドまで含めて書いてください。Bash(npx wrangler *)の一括許可は避けます。
それでもbash -cのような形は残ります。ここを塞ぐには、コマンド全文を検査するPreToolUseフックが向いています。
PreToolUseフックで最後の網を張る
フックはBashの実行前に呼ばれ、標準入力にツール入力のJSONを受け取ります。終了コード2を返すと、その呼び出しは拒否されます。allowルールがあっても、フックの拒否が先に効きます。
.claude/hooks/block-wrangler-prod.shの例です。
#!/usr/bin/env bash
cmd=$(jq -r '.tool_input.command // empty')
# 本番へ届くwranglerコマンドと、本番用スクリプトを検出
pattern='wrangler[[:space:]]+(deploy|delete|rollback|secret|triggers[[:space:]]+deploy|versions[[:space:]]+deploy)|deploy:production'
if echo "$cmd" | grep -Eq "$pattern"; then
if ! echo "$cmd" | grep -Eq -- '--dry-run|--env[ =]staging|deploy:staging'; then
echo "本番に届くコマンドです。人が別ターミナルで実行してください" >&2
exit 2
fi
fi
exit 0settings.jsonには次のように登録します。
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/block-wrangler-prod.sh"
}
]
}
]
}
}本番デプロイを止める2つの設計
askだけ
deploy:productionは毎回承認プロンプトに出ます。Claudeに実行を提案させ、人が画面で許可する流れです。
フックを足す
終了コード2の拒否が承認プロンプトより先に効くので、deploy:productionは承認の画面にすら届きません。本番を人の別ターミナルに完全に残す設計です。
両方を重ねるとフックが先に効くため、askの承認は使われません。どちらか一方を選びます。
なお、このフックの除外判定はコマンド全文への部分一致です。npx wrangler deploy && echo --dry-runのように文字列を後ろに足す書き方までは防げません。偶発的な事故を減らす網であって、意図的な迂回を防ぐ仕組みではありません。
フックが返したallowやaskは、denyとaskのルールを覆せません。一方で終了コード2の拒否は、標準出力にpermissionDecision: "allow"のJSONを出していても覆りません。フックの結果が必要なのは「拒否」の側だけと割り切れば、スクリプトは短くなります。
認証情報をClaudeの手元に置かない
権限ルールとは別に、Claudeのセッションが持つ資格情報の範囲も設計対象です。
資格情報の置き場所で効く3点
ログインのスコープ
wrangler loginはOAuthでログインします。スコープを指定しないと、利用可能なすべてのスコープで認可されます。--scopes account:read user:readのように空白区切りで絞れます。APIトークンが優先
CLOUDFLARE_API_TOKENが設定されていると、保存済みのOAuth資格情報より優先されます。トークンの権限をステージング用のWorkerに絞ったうえでセッションの環境変数に置けば、本番のdeployは認可エラーで止まります。.dev.varsと.env
ローカル開発のシークレットは、Wrangler設定ファイルと同じ場所の
.dev.varsか.envに置きます。環境名つきの.dev.vars.<環境名>も選ばれて読まれます。上の設定例ではReadをdenyにしました。
ただしReadのdenyが効くのは、Claudeの組み込みファイルツールと、catのように認識できるBashコマンドです。Nodeスクリプトが自分でファイルを開く場合は対象外です。厳密に塞ぐなら、サンドボックスを併用します。
同じ理由で、allowに入れたwrangler typesやwrangler dev --localは、Readのdenyと無関係にこれらのファイルを自分で読みます。wrangler typesはシークレット名を.dev.varsや.envから推測します。設定ファイルにsecrets.requiredを宣言すると、推測をやめて宣言した名前だけから型を作り、ファイルが無い環境でも動きます。devも、secrets.requiredにある名前だけを読み込みます。
なお、認証がまだ無い環境向けにwrangler deploy --temporaryがあります。Wrangler 4.102.0以降で使え、一時的なプレビュー用アカウントへデプロイし、60分以内に引き取るためのclaim用URLを出力します。資格情報が既にあるとエラーになる仕様です。AIエージェントの初回デプロイ向けと説明されているので、この経路を許可するかどうかも、上の3段のどこに置くか決めておきます。
Claudeに渡すCLAUDE.mdの断片
権限ルールは、Claudeが何を試みるかを変えません。試みた結果をどう扱うかを決めるだけです。試行の段階で外れを減らすため、CLAUDE.mdに方針を書いておきます。
## Cloudflare Workers
- ローカル確認は `npm run dev`(wrangler dev --local)を使う
- デプロイ前の検証は `npm run check:deploy`
- ステージングへは `npm run deploy:staging`
- `wrangler deploy` `wrangler secret` `wrangler delete` を直接実行しない
- 本番反映が必要なときは、変更内容と `npm run deploy:production` の実行を提案するCLAUDE.mdは強制力を持ちません。境界を守らせているのはsettings.jsonのルールとフックで、CLAUDE.mdはClaudeが承認待ちの弾かれ方を減らすための説明です。
allowで通した先で回す開発ループ
allowに入れたコマンドは、Claudeが確認なしで何度でも回せます。この自由度を、検証の反復に使います。
Bindingを変えたら型を作り直す
wrangler typesは、Worker設定のbindingからEnv型を生成します。既定では設定ファイルにあるすべての環境のbindingを含み、片方の環境にしかないKVやR2は省略可能なプロパティとして出力されます。--envを付けると特定の環境だけに絞れます。wrangler.jsoncをEditした直後に型を更新させれば、型エラーをテストの出力として読み返させる流れになります。
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "jq -r '.tool_input.file_path' | grep -q 'wrangler.jsonc$' && npx wrangler types || true"
}
]
}
]
}
}ローカルで起動して叩く
wrangler dev --localは既定でlocalhostの待ち受けです。--portで番号を固定でき、Cron Triggerのテストには--test-scheduledを付けると、/cdn-cgi/local/scheduledへのfetchでscheduledイベントを起こせます。Claudeに「--port 8787で起動し、curlで応答を確認して、失敗したら直す」と頼めば、起動から確認までが承認なしで回ります。
起動時間を測る
wrangler check startupは、バンドルサイズと起動フェーズのローカルCPU活動を要約し、詳細なCPUプロファイルを保存します。ただし測定はローカルのマシンで行うため、Cloudflare上の起動時間とは一致しません。デプロイが起動時間のエラーで失敗したときは、Wranglerがこのプロファイルを自動で生成します。設定例のallowにBash(npx wrangler check startup)を入れてあるので、失敗の原因調査はClaudeに任せられます。
運用で効く調整
wrangler tailは判断が分かれる
ログ表示セッションを開始するコマンドで、本番リクエストのログが画面に出ます。ログに個人情報が含まれないならallow、含まれうるならaskのままにします。出力を絞る手もあります。--status errorでエラーだけに、--searchでconsole.logの文字列一致に、--ip selfで自分のIPからのリクエストだけに絞れます。
versions uploadで反映の段階を分ける
versions uploadは新バージョンを作るだけで、すぐには反映しません。Claudeにバージョンを作らせ、versions deployはaskで人が承認する流れにできます。versions deployには--percentageもあり、トラフィックの一部だけに流す段階的な反映もできます。
--strictで非対話の事故を減らす
wrangler deploy --strictは、デプロイが非対話環境でリモート側の設定を上書きしうるとき、実行そのものを止めます。ダッシュボードで変えた環境変数をClaude経由のデプロイが消す事故は、--keep-varsと合わせて防げます。deploy:stagingのスクリプトに--strictを足すと、この種の事故を入口で止められます。
auto modeでもaskは残る
askルールは複合コマンドの一部にマッチしたときも、auto modeで確認を求めます。承認の細かい編集方法はClaude CodeのAuto modeルールを編集するにまとめています。
設計を確かめる手順
設定を書いたら、実際に弾かれるかを確かめてから作業を任せます。
設定が効いているかを確かめる4手順
- 1
/permissionsでルールを見る
ルールと参照元の
settings.jsonが意図どおりかを確かめます。 - 2
denyが止めるかを試す
Claudeに
npx wrangler deploy --dry-runを頼み、denyで止まることを確かめます。 - 3
allowが通るかを試す
同じく
npm run check:deployを頼み、承認なしで通ることを確かめます。 - 4
本番スクリプトの止まり方を見る
npm run deploy:productionを頼み、承認プロンプトかフックの拒否が出ることを確かめます。
手順2と3は、同じドライランが経路によって結果が変わることを示します。この差が、スクリプト名を境界にする設計の要点です。
まとめ
Workers開発の権限設計は、コマンドを「ローカルで完結」「本番に届く」「消える」に分けるところから始まります。ポイントは3つです。
secret putは即デプロイなので、deployと同じ段階に置くaskはallowより先に評価されるため、例外を作りたいコマンドはnpm runのスクリプトに包むbash -cのような迂回はルールで止まらないので、PreToolUseフックと限定トークンを併用する
MCPサーバー経由でCloudflareを操作する場合の権限は、PaaS系MCPの本番デプロイ権限を各社でどう絞るかが扱っています。権限ルール全体の書き方はClaude Code settings.json完全ガイド、MCP側の構成はCloudflare MCPサーバー一覧を参照してください。