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 dev --remote | 何をするかCloudflare上のリモートリソースとデータを使う | 段階ask |
wrangler secret put / bulk | 何をするかシークレット更新。新バージョンを作って即デプロイ | 段階ask |
wrangler versions deploy | 何をするか作成済みバージョンを反映 | 段階ask |
wrangler rollback | 何をするか指定バージョンを全ルートで即座に有効化 | 段階ask |
wrangler deploy | 何をするかWorkerを本番へデプロイ | 段階ask(下の設計参照) |
wrangler delete | 何をするかWorkerと関連リソースをすべて削除 | 段階deny |
見落としやすいのはsecret putです。公式の説明では、このコマンドはWorkerの新しいバージョンを作り、そのまま即座にデプロイします。名前は「設定」ですが、本番反映の性質はdeployと同じです。
wrangler devは既定でローカル実行ですが、--localを付けるとリモートbindingがすべて無効になり、remote: falseと同じ挙動になります。設定ファイル側でbindingをremoteにしていても、--localがあれば本番のD1やKVには触れません。Claudeに書かせるコマンドは--local付きに寄せるのが安全です。
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(npm run check:deploy)",
"Bash(npm run deploy:staging)"
],
"ask": [
"Bash(npx wrangler dev --remote *)",
"Bash(npx wrangler secret *)",
"Bash(npx wrangler versions 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も同じルールで止まります。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|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"
}
]
}
]
}
}このフックはaskと役割が違います。askは「承認すれば通る」設計で、フックは「Claudeからは通さない」設計です。両方を重ねると、deploy:productionは承認プロンプトの前にフックで止まります。本番を人の手に完全に残すならフック、Claudeに提案させて承認で通したいならaskだけにします。どちらかを選んでください。
フックが返したallowやaskは、denyとaskのルールを覆せません。一方で終了コード2の拒否は、allowルールに先立って呼び出しを止めます。
認証情報をClaudeの手元に置かない
権限ルールとは別に、Claudeのセッションが持つ資格情報の範囲も設計対象です。
wrangler loginはOAuthでログインします。スコープを指定しないと、利用可能なすべてのスコープで認可されます。--scopesで絞れますCLOUDFLARE_API_TOKENが設定されていると、保存済みのOAuth資格情報より優先されます。ステージング専用のトークンをセッションの環境変数に置けば、本番のdeployはそもそも認可エラーになります- ローカルの
.dev.varsや.envは、Wranglerが環境名で選んで読むファイルです。上の設定例ではReadをdenyにしました
ただしReadのdenyが効くのは、Claudeの組み込みファイルツールと、catのように認識できるBashコマンドです。Nodeスクリプトが自分でファイルを開く場合は対象外です。厳密に塞ぐなら、サンドボックスを併用します。
なお、認証がまだ無い環境向けに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のままにします。
versions uploadをallowに置くと、反映の段階を分けられます。 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ルールを編集するにまとめています。
設計を確かめる手順
設定を書いたら、実際に弾かれるかを確かめてから作業を任せます。
/permissionsを開き、ルールと参照元のsettings.jsonが意図どおりか見る- Claudeに
npx wrangler deploy --dry-runを頼み、denyで止まることを確認する - 同じく
npm run check:deployを頼み、承認なしで通ることを確認する 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サーバー一覧を参照してください。