Claude CodeでAzure Functionsを開発する — func startとpublishの権限設計
func startは通し、func azure functionapp publishは止める。Claude Codeのpermissionsとフックで、Azure Functionsのローカル検証と本番反映を分ける設定例をまとめます。
Claude CodeにAzure Functionsを開発させるなら、最初に決めるのはfuncコマンドのどこまでを自動で通すかです。func startはローカルでホストを起動するだけで、何度走っても本番には触れません。一方でfunc azure functionapp publishは、既存の関数アプリにプロジェクトを配備します。
この記事は、ローカル検証とpublishを権限の面で切り離す設定例です。許可するのは検証まで。配備と、配備に付随する設定の同期は人の手に残します。
Lambda(SAM)やCloudflare Workersで同じ問題を扱った記事と構造は似ています。違うのは、Azure Functionsにはlocal.settings.jsonという秘密の置き場があり、publishがそれを本番へ送れる点です。ここが設計の中心になります。同系統の記事としてClaude CodeでCloudflare Workersを開発するもあります。
funcコマンドを3段に分ける
Azure Functions Core Tools(v4)のコマンドは、コンテキストごとに整理されています。func直下がローカルの作成と実行、func azureがAzureリソースの操作で、publishもここにあります。リファレンスの説明をもとに、Claude Codeの権限へ割り当てるとこうなります。
| コマンド | 何をするか | 段階 |
|---|---|---|
func init / func new | 何をするかプロジェクトと関数をローカルに作る | 段階allow |
func start | 何をするかローカルのホストを起動(既定ポート7071) | 段階allow |
func pack | 何をするか配備用のzipをローカルで作る。配備はしない | 段階allow |
func settings list | 何をするかlocal.settings.jsonの設定名を一覧(値は既定でマスク) | 段階allow |
func settings add / delete | 何をするかValuesの設定を書き換える | 段階ask |
func settings decrypt | 何をするか暗号化された値を平文に戻す | 段階ask |
func azure functionapp fetch-app-settings | 何をするかAzure上の設定をlocal.settings.jsonへ落とす | 段階ask |
func azure functionapp logstream | 何をするかAzure上のログを手元に流す | 段階ask |
func azure functionapp list-functions | 何をするかAzure上の関数を一覧する | 段階ask |
func azure functionapp publish | 何をするかプロジェクトを関数アプリへ配備する | 段階deny |
func azure storage fetch-connection-string | 何をするかストレージの接続文字列を取得する | 段階deny |
func azurecontainerapps deploy | 何をするかコンテナ化した関数アプリを配備する | 段階deny |
func kubernetes deploy | 何をするかカスタムコンテナをKubernetesクラスターへ配備する | 段階deny |
表のうちallowに置いた4種は、いずれもローカルで完結します。func packは、func azure functionapp publishの外で手動のデプロイパッケージを作るコマンドです。パッケージを作るところまでで、Azureへは送りません。
askの行は、Azureに接続するか、秘密の値を扱うものです。fetch-app-settingsは本番の設定を手元のファイルに書き込みます。画面上の値はマスクされますが、ファイルには値が入ります。
list-functionsは読み取りだけに見えます。ただし--show-keysを付けると、関数レベルのアクセスキーがURLに含まれて返ります。コマンドの見た目より、オプション一つで性質が変わる例です。
local.settings.jsonが本番に届く経路
local.settings.jsonには、アプリ設定とローカルツール用の設定が入ります。秘密を含みうるので、ソース管理へのコミットには注意が要ります。プロジェクトの.gitignoreは、このファイルが誤ってGitリポジトリに入るのを防ぐ役目です。
{
"IsEncrypted": false,
"Values": {
"FUNCTIONS_WORKER_RUNTIME": "node",
"AzureWebJobsStorage": "UseDevelopmentStorage=true"
},
"Host": {
"LocalHttpPort": 7071,
"CORS": "*"
}
}これはドキュメントのファイル構造に沿った最小の形です。Valuesの設定名は、Azureの関数アプリ設定と対応します。
ここで問題になるのがpublishのオプションです。--publish-local-settings(-i)は、local.settings.jsonの設定をAzureへ送ります。既存の設定があれば上書きの確認が出ますが、--overwrite-settings(-y)を付けるとその確認が消えます。--publish-settings-only(-o)なら、コードを送らず設定だけが動きます。
つまり、publishをdenyにするのはコードの配備を止めるためだけではありません。ローカルの値で本番の設定を書き換える経路も、同じルールで閉じます。
Claudeにlocal.settings.jsonを読ませるかどうかは別の問いです。接続文字列が入っているなら、Readをdenyに置きます。
{
"permissions": {
"deny": [
"Read(./local.settings.json)",
"Edit(./local.settings.json)"
]
}
}ただし、このdenyが効くのはClaudeの組み込みファイルツールと、catのように認識できるコマンドです。func startはこのファイルを自分で読み込みます。ホストの起動に必要な値が入っているので、allowしたfunc startとReadのdenyは両立します。
Editも止めると、設定の追加はfunc settings add経由になります。func settingsはコマンド側でValuesを書き換えるため、askに置いてあれば人が内容を確認できます。
settings.jsonの設定例
Claude Codeの権限ルールはdeny、ask、allowの順に評価され、最初に当たったものが結果です。広いdenyは、狭いallowに当たるコマンドも止めます。allowで穴を開けたいコマンドは、denyやaskと重ならない書き方にします。
{
"permissions": {
"allow": [
"Bash(func init *)",
"Bash(func new *)",
"Bash(func start *)",
"Bash(func pack *)",
"Bash(func settings list *)",
"Bash(func templates list *)",
"Bash(npm run build)",
"Bash(npm test)"
],
"ask": [
"Bash(func settings add *)",
"Bash(func settings decrypt *)",
"Bash(func azure functionapp fetch-app-settings *)",
"Bash(func azure functionapp logstream *)",
"Bash(func azure functionapp list-functions *)",
"Edit(/host.json)",
"Edit(/package.json)"
],
"deny": [
"Bash(func azure functionapp publish *)",
"Bash(func azure storage *)",
"Bash(func azurecontainerapps *)",
"Bash(func kubernetes *)",
"Bash(func deploy *)",
"Read(./local.settings.json)",
"Edit(./local.settings.json)"
]
}
}末尾の*の前には空白が必要です。Bash(func start *)はfunc startとfunc start --port 7072の両方に当たり、func startxには当たりません。Claude Codeのルール説明にある書き方です。
func azure functionapp publishはdenyなので、--list-included-filesのような確認用オプションも同じルールで止まります。denyから例外は切り出せません。配備に含まれるファイルの確認が要るなら、人が自分の端末で実行します。
func azure storage *とfunc azurecontainerapps *は、使わないプロジェクトならdenyに置いておくと設定が減ります。func deployは非推奨のコマンドで、代わりはfunc kubernetes deployです。両方を塞いでおきます。
func startで回す検証ループ
allowに入れたfunc startは、確認なしで何度でも起動できます。この自由度を検証の反復に使います。
起動は長く走るプロセスです。Claude Codeには、Bashの呼び出しをバックグラウンドで走らせる仕組みがあります。ホストを立てたまま、別のコマンドでエンドポイントを叩く流れになります。
func start --port 7071 --user-log-level Information別の呼び出しで、HTTPトリガーの関数を確認します。
curl -s -i http://localhost:7071/api/HttpExample?name=ClaudeHttpExampleはテンプレートが生成する関数名の例です。実際の名前はfunc newで付けたものに置き換えます。
func startには、検証で使いどころのあるオプションがいくつかあります。
| オプション | 効果 |
|---|---|
--port、-p | 効果待ち受けポートを変える(既定7071) |
--functions | 効果読み込む関数を空白区切りで絞る |
--user-log-level | 効果ユーザーログの最小レベル(Trace〜None)。システムログには効かない |
--timeout、-t | 効果ホストの起動待ちの秒数(既定20秒) |
--enableAuth | 効果認証の処理パイプラインを有効にする |
--no-build | 効果起動前にビルドしない(.NETのクラスプロジェクトのみ) |
見落としやすいのは認証です。func newの--authlevelにはfunction、anonymous、adminがありますが、ローカル実行では認可は強制されません。functionレベルの関数も、ローカルではキー無しで呼べます。
認可の挙動まで検証したいときは--enableAuthを付けます。キー無しの呼び出しが拒否されることを確認してから、配備を人に頼む流れが組めます。
--corsと--cors-credentialsは、ブラウザから呼ぶ関数の確認に使います。local.settings.jsonのHostセクションにもCORSとLocalHttpPortがありますが、ポートはコマンドラインの--portが優先されます。
.NETの分離ワーカーなら、--dotnet-isolated-debugをtrueにするとワーカーがデバッガーの接続を待ちます。Claudeに起動させる用途には向かないので、人がデバッグするときの手段として覚えておく程度で足ります。
Azuriteで本番のストレージに触れない
HTTP以外のトリガー(キュー、BLOB、タイマーなど)は、AzureWebJobsStorageが必要です。Azuriteが入っていてUseDevelopmentStorage=trueを設定すると、ローカルのホストがエミュレーターを使います。
逆に、Valuesに本物のストレージの接続文字列を入れると、ローカルの実行が本番サービスに影響しうる、と注意書きがあります。Claudeにfunc startを自由に回させるなら、この値は開発用のストレージかAzuriteにします。本番の接続文字列は、fetch-app-settingsのaskを通さない限り手元に来ない状態が安全です。
もう一つ、明記されている注意があります。配備時にAzureWebJobsStorageをUseDevelopmentStorage=trueのまま送ってはいけません。Azureでは、関数アプリが使うストレージアカウントの接続文字列でなければならないためです。-i付きのpublishをdenyにしておくと、この取り違えも防げます。
プレビュー版のv5(Azure Functions CLI)では、func runがローカルのホストを起動します。func startは互換のエイリアスです。ローカルストレージを使うプロジェクトなら、func runが起動中のAzuriteを探し、無ければ自動で起動します。終了時に止まります。--no-azuriteで無効にできます。
v5を使う場合は、ルールにBash(func run *)も追加します。func startだけをallowにしていると、func runは承認待ちに回ります。
publishを人に残す設計
配備の手順は、人の端末かCIに置きます。Claudeには「配備前の検証」と「配備コマンドの提案」までを任せます。検証の中身は次のとおりです。
func packでzipを作り、ビルドと依存の解決が通ることを確認するfunc startで起動し、curlで主要なエンドポイントの応答を確認する- テストを実行する(
npm testなど) - 変更内容の要約と、人が実行する
func azure functionapp publish <アプリ名>をClaudeが提示する
func packの--no-buildは、Node.jsならnpm installとnpm run buildの両方を飛ばします。検証が目的なので、このオプションは付けません。ビルドを含めて確かめることに意味があります。
PreToolUseフックで最後の網を張る
denyルールは、Claudeが普段書く形を止めるものです。bash -c 'func azure functionapp publish ...'のような包み方は、ルールの照合から外れることがあります。
PreToolUseフックは、実行前にBashコマンドの全文を受け取ります。終了コード2を返すと、その呼び出しはブロックされ、標準エラーの文面がClaudeに返ります。
.claude/hooks/block-func-publish.shの例です。
#!/usr/bin/env bash
cmd=$(jq -r '.tool_input.command // empty')
# 本番へ届く func コマンドを検出
pattern='func[[:space:]]+(azure[[:space:]]+functionapp[[:space:]]+(publish|fetch-app-settings)|azurecontainerapps|kubernetes[[:space:]]+deploy|deploy)|--publish-local-settings|--publish-settings-only'
if echo "$cmd" | grep -Eq "$pattern"; then
echo "本番に届く func コマンドです。人が別ターミナルで実行してください" >&2
exit 2
fi
exit 0settings.jsonへの登録は次の形です。
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/block-func-publish.sh"
}
]
}
]
}
}この例はfetch-app-settingsも止めるので、askルールに置いた同コマンドは承認の画面に届きません。承認で通したいなら、フックのパターンから外します。
フックは部分一致で見ているだけです。意図的な迂回までは防げません。事故を減らす最後の網という位置づけです。厳密に閉じるなら、サンドボックスや、Azure側の資格情報の範囲で縛ります。
資格情報をClaudeの手元に置かない
権限ルールとは別に、そもそも配備できる資格情報を持たせない設計もあります。func azure functionappの各コマンドは--access-tokenや--access-token-stdinを受け取り、--subscriptionで対象のサブスクリプションも指定できます。
Claudeのセッションに、本番サブスクリプションの書き込み権限を持つログイン状態を引き継がないことが、ルールより確実な防御です。ルールの記述ミスがあっても、資格情報が無ければ配備は通りません。
また--access-tokenをコマンドラインに直接渡す形は、トークンがコマンド履歴や承認画面に出ます。標準入力から渡す--access-token-stdinのほうが、画面に残る情報は少なくなります。
CLAUDE.mdに書く断片
権限ルールは、Claudeが何を試みるかを変えません。試みた結果をどう扱うかを決めるだけです。弾かれる回数を減らすため、CLAUDE.mdに方針を書いておきます。
## Azure Functions
- ローカル確認は `func start` を使う。起動したまま別の呼び出しで curl する
- 配備前の検証は `func pack` と `npm test`
- `local.settings.json` を読まない。設定の追加は `func settings add`
- `func azure functionapp publish` と、`-i` `-o` 付きのpublishは実行しない
- 配備が必要なときは、変更内容と publish コマンドを提案して止まるCLAUDE.mdは強制力を持ちません。境界を守らせているのはsettings.jsonのルールとフックです。
つまずきやすい点
- ホストが20秒で起動に失敗する: 重いプロジェクトでは
--timeoutで起動待ちを延ばします。既定は20秒です - ポートが埋まっている: 前回の
func startが残っていることがあります。--portで別のポートにするか、残ったプロセスを止めます npm start経由だとルールに当たらない: プロジェクトによってはpackage.jsonのstartスクリプトがfunc startを呼びます。権限ルールが見るのはClaudeが書いたコマンド文字列なので、Bash(npm start)を別にallowします。中身がfunc startだけであることを、スクリプトの定義で確かめておきます- 非HTTPトリガーが動かない:
AzureWebJobsStorageが未設定か、Azuriteが起動していません。v4ではAzuriteを自分で立てます - 設定の暗号化:
IsEncryptedがtrueのとき、値は読めない形で保存されています。func settings decryptは平文に戻すので、askに置いてあるとおり、実行前に人が確認します - 関数が多すぎて起動が遅い:
--functionsで、確認したい関数だけに絞れます
まとめ
Azure Functionsの権限設計は、func startとfunc packをClaudeに任せ、func azure functionapp publishと設定の同期を人に残す、という分け方に落ち着きます。allowは検証まで、askはAzureに接続する読み取り、denyは配備とストレージ接続文字列の取得です。
見落としやすいのは、publishがlocal.settings.jsonの値を本番へ送れる点と、--show-keysのようにオプションで性質が変わる点です。denyでpublishごと閉じ、フックで包み方の違いを拾い、本番の資格情報はそもそも持たせない。この3層で、ローカルの検証ループは自由に回しつつ、本番には届かない構成になります。
同じ型の権限設計はほかの言語にも広がります。権限フラグをタスクに固定する形はClaude CodeでDenoを開発するで扱っています。Claude Code全体の使い方から確認したい場合は、そちらから入るのが近道です。