Claude CodeでAWS Lambda(SAM)を開発 — local invokeで検証しdeployは承認制に
Claude CodeにAWS SAMのLambdaを書かせるとき、sam validate・build・local invokeを自走の検証ループにし、sam deployだけ確認を挟む権限設定とCLAUDE.mdの書き方を示します。
Claude CodeにAWS SAMのLambdaを書かせるなら、sam validate・sam build・sam local invokeは確認なしで回し、sam deployだけ毎回人が承認する構成が扱いやすくなります。前者は手元で完結し、後者はAWSアカウントに変更を加えるからです。この記事は、その線引きを.claude/settings.jsonとCLAUDE.mdに落とす手順をまとめます。
コマンドを「手元で閉じるもの」と「AWSに触るもの」に分ける
SAM CLIのコマンドは、作用する場所で3つに分けられます。分け方が権限ルールの設計そのものになります。
作用先で分けた3区分
静的検証
sam validateはSAMテンプレートが正しいかを確認します。作用先はテンプレートのファイルだけです。
ローカル実行
sam buildとsam local invokeが入ります。local invokeはLambda関数を1回だけ、手元のDockerコンテナで呼び出します。
AWSへの反映
sam deployはCloudFormationを使ってアプリケーションをAWSクラウドへデプロイします。作用先はAWSアカウントです。
Claude CodeのBashは、組み込みの読み取り専用コマンド(lsやcatなど)を除いて承認が要ります。samはその例に挙がっていないので、何も設定しなければ検証コマンドまで毎回確認が出ます。検証は自走させたいので、allowで通します。
settings.jsonに書く権限ルール
プロジェクトの.claude/settings.jsonに次のように置きます。
{
"permissions": {
"allow": [
"Bash(sam validate *)",
"Bash(sam build *)",
"Bash(sam local invoke *)"
],
"ask": [
"Bash(sam deploy *)"
],
"deny": [
"Bash(sam deploy *--no-confirm-changeset*)",
"Bash(sam deploy *--disable-rollback*)"
]
}
}ルールの読み方を確認しておきます。
- 評価順はdeny、ask、allowの順で、最初に一致したものが結果を決めます。askに一致するコマンドは、より狭いallowがあっても確認が出ます
- 末尾の
*は、前にスペースがあると引数なしの裸のコマンドにも一致します。Bash(sam deploy *)はsam deploy単体も拾います *はサブコマンドの後ろに置きます。Bash(sam * deploy)のように前へ置くと、意図しないコマンドまで一致します
denyの2行は、--no-confirm-changesetと--disable-rollbackを締め出すための行です。denyがaskより先に評価されるため、これらのフラグを付けたsam deployは確認画面に進まず拒否されます。
--disable-rollbackを外すのは、失敗時の後始末が変わるからです。既定ではデプロイ中のエラーでスタックが直前の安定状態へ戻ります。このフラグを付けると、エラーまでに作成・更新されたリソースがそのまま残ります。
環境変数の前置きも扱いに違いがあります。AWS_PROFILE=dev sam deployのように前へ変数代入を付けても、askとdenyのルールはその代入を飛び越えて一致します。一方allowは、既知の安全な変数以外の代入を前置きすると一致しません。既知の安全な変数の一覧は公開されていないため、AWS_PROFILEを前置きしたsam local invokeではallowが効かず、確認が出ることがあります。変数で渡す代わりに--profileで指定すると、この影響を避けられます。
CLAUDE.mdに検証の順序を書く
権限は「できること」を決め、CLAUDE.mdは「やる順序」を決めます。両方そろえると、Claudeが自分で検証を反復します。
## AWS SAM(Lambda)の作業手順
テンプレートやハンドラーを変えたら、次の順で実行して結果を読む。
1. `sam validate --lint`(テンプレートの検証)
2. `sam build`(ビルドが通ること)
3. `sam local invoke <関数の論理ID> -e events/<名前>.json`
- イベントは`events/`のJSONを使う。無い場合は先に作る
- 環境変数は`env.json`を`-n`で渡す。実際のシークレットは書かない
- `sam deploy --no-execute-changeset`は承認を得て実行し、変更セットの要約を示す
- 変更を適用する`sam deploy`は自分から提案せず、人が要約を読んで依頼したときだけ実行する
- `--no-confirm-changeset`と`--guided`は使わないsam validateの--lintは、cfn-lintによるリンティングも合わせて走らせるオプションです。追加の設定はcfnlintrcファイルで指定します。テンプレートの文法エラーだけでなく、CloudFormation側の指摘まで手元で受けられるので、Claudeの修正の入口として向いています。
検証ループの中身
validateとbuild
sam validate --lint
sam buildsam validateは、カレントディレクトリにあるtemplate.yaml(.ymlや.jsonも可)を既定で読みます。別の場所なら--template-file(-t)で指定します。sam buildを直前に実行していれば、このオプションも不要です。
ネイティブにコンパイルされた依存パッケージを含む関数は、sam build --use-container(-u)でLambdaに近いDockerコンテナ内でビルドします。逆に--no-use-containerを付けると手元のマシンでビルドします。どちらを使うかはランタイムと依存によって変わるので、CLAUDE.mdに一行書いておくと迷いません。
local invokeでハンドラーを実際に動かす
sam local invoke HelloWorldFunction -e events/apigw-get.json引数は関数の論理IDです。テンプレートに関数が1つしかなければ省略でき、複数あるときは指定が必要です。
イベントの渡し方は次のとおりです。
| 目的 | オプション |
|---|---|
| JSONファイルのイベントを渡す | オプション--event(-e)events/x.json |
| 標準入力から渡す | オプション-e - |
| 空のイベントで呼ぶ | オプション--no-event |
| 環境変数を渡す | オプション--env-vars(-n)env.json |
--eventを指定しない場合、イベントは渡されません。S3など他サービスのイベント形式は、AWS Lambdaの開発者ガイドが定義しています。イベントJSONはevents/の下にファイルとして置いておくと、同じ入力で何度でも再現できます。sam local generate-eventというサブコマンドもコマンド一覧に載っています。
Claudeへは、出力を読ませたうえで失敗時の修正まで任せます。ステータスコードや例外のスタックトレースが返るので、ハンドラーの修正、再ビルド、再実行が1ターンの中で回ります。
ローカル実行の前提と制約
sam local invokeは、Lambdaのランタイムに近いイメージをDockerコンテナとして起動して関数を動かします。既定ではLambdaの最新のリモートランタイム環境と同期するため、ローカルのイメージを自動更新します。--skip-pull-imageを付けるとこの取得を省けます。毎回の取得を省きたいときに使います。
AWSのドキュメントは、信頼できないコードに対してSAM CLIのローカル呼び出しを使うことを勧めていません。手元の環境から完全に隔離して動かしたいなら、Lambdaサービス上で直接実行するよう案内されています。Claudeが外部から取り込んだコードをsam local invokeで動かす流れは避け、自分のリポジトリのハンドラーに限る運用にすると安心です。
もう一点。allowに入れたBash(sam local invoke *)は、--profileや--regionを付けても通ります。sam local invokeにAWSの認証情報を渡す必要がない場合(ハンドラーが他のAWSサービスを呼ばない場合)は、CLAUDE.mdに「--profileは付けない」と書いて、資格情報の行き場を減らしておきます。
deployを承認制にする
Bash(sam deploy *)をaskに置くと、Claudeがsam deployを実行しようとするたびに確認ダイアログが出ます。承認を毎回求める点が、この設定の要です。ダイアログで「Yes, and don't ask again」を選ぶと、そのコマンドのallowルールが.claude/settings.local.jsonに保存されます。ただし評価順はaskがallowより先なので、askルールが残っている限り、保存後も確認は出続けます。
変更セットを先に見せる
sam deployには、変更を適用せずに確認だけするオプションがあります。
sam deploy --no-execute-changesetこのオプションは、CloudFormationの変更セットを作成して終了します。適用するには、同じコマンドを--no-execute-changesetなしで再実行します。何が作られ、何が置き換わり、何が削除されるのかを、人が読める形で先に出せます。
--no-execute-changesetもAWSへ変更セットを作るコマンドなので、askの対象に含まれます。確認なしで実行できるわけではなく、承認は2回あります。
deployが人の手に渡るまで
- 1
変更セットだけ作る
Claudeが
sam deploy --no-execute-changesetを提案し、1回目の承認ダイアログが出ます。 - 2
Claudeが差分を要約する
作られるもの、置き換わるもの、削除されるもの、IAMリソースの有無を挙げさせます。
- 3
人が読んで依頼する
要約に問題がなければ、人が適用を依頼します。
- 4
同じコマンドで適用する
--no-execute-changesetを外したsam deployで、2回目の承認ダイアログが出ます。
confirm-changesetと--no-confirm-changeset
--confirm-changesetは、計算された変更セットをSAM CLIがデプロイしてよいか、実行前にプロンプトで確認するオプションです。反対の--no-confirm-changesetを付けると、確認が省かれます。自動化のスクリプトではよく使われるフラグですが、承認制にしたいこのリポジトリではClaudeに使わせたくありません。
先のdenyルールBash(sam deploy *--no-confirm-changeset*)は、その対策です。--guided(対話形式のデプロイ)も同様に、Claudeが扱うと対話待ちで止まるので、CLAUDE.md側で使わない旨を書きます。
IAMを作る変更は--capabilitiesで明示される
テンプレートがIAMリソースを含むと、sam deployには--capabilities CAPABILITY_IAM(名前付きならCAPABILITY_NAMED_IAM)が必要です。指定しないとInsufficientCapabilitiesエラーになります。ネストしたアプリケーションを含むときはCAPABILITY_AUTO_EXPANDです。
承認ダイアログのコマンド文字列にこのオプションがあれば、IAMを作る変更が含まれるサインになります。ただし次節のとおり、samconfig.tomlに書かれていると文字列には出ません。目印をコマンド行だけに頼らず、CLAUDE.mdに「IAMを伴うdeployは、変更セットの要約でIAMリソースを必ず挙げる」と書いておくと、確認の質が上がります。
samconfig.tomlは承認画面に映らない
sam deployの既定値は、プロジェクト直下のsamconfig.tomlから読まれます。--config-fileで別の場所も指せ、--config-envで[prod]のような環境の節も選べます。AWSのドキュメントにある設定例は次のとおりです。
version = 0.1
[default.global.parameters]
stack_name = "sam-app"
[default.deploy.parameters]
capabilities = "CAPABILITY_IAM"
confirm_changeset = true
resolve_s3 = trueコマンドラインで渡した値は設定ファイルの値より優先されます。つまりこの設定があると、sam deployと打つだけでスタック名、--capabilities、変更セットの確認、S3バケットの自動作成が決まります。承認ダイアログに出るのは打った文字列だけなので、これらは映りません。
もう一つの穴は、confirm_changeset = falseが書き込まれた場合です。コマンド文字列に--no-confirm-changesetが無いため、先のdenyに一致せず、変更セットの確認なしでデプロイが走ります。
ファイル編集の側にも、askを置いておきます。承認ダイアログに映らないsamconfig.tomlの書き換えを、ここで人の目に通すためです。先頭が/のパスは、プロジェクト設定では作業ディレクトリ基準です。Edit(/samconfig.*)はsamconfig.tomlとsamconfig.yamlの両方に一致します。
{
"permissions": {
"ask": [
"Edit(/samconfig.*)"
]
}
}このaskは、Claudeの組み込みのファイル編集ツールに掛かります。sedやリダイレクトなどBashのファイル操作にまで及ぶと書かれているのはdenyルールなので、Bash経由の書き換えも止めたいならdenyに置きます。一方、PythonやNodeのスクリプトが自分でファイルを書く経路は止まりません。確実に守るなら、サンドボックスかAWS側の権限が要ります。
ルールだけでは止められない呼び方
Bashルールは、Claudeが書いたコマンド文字列に一致させる仕組みで、そのプログラムに対する安全境界ではありません。Bash(sam deploy *)のaskが止めるのは、sam deploy ...という通常の書き方です。/usr/local/bin/sam deployやbash -c 'sam deploy'のように別の形で呼ばれた場合、このルールは一致しません。他のルールと権限モードが結果を決めます。
対策は3つあります。
迂回への備え
PreToolUseフックで全文を検査する
フックは権限プロンプトの前に走り、終了コード2でBashの実行を止められます。
サンドボックスを併用する
ファイルシステムとネットワークへのアクセスをOSレベルで制限します。プロンプトインジェクションでClaudeの判断が崩れても、サンドボックスの制限は残ります。
AWS側の権限を絞る
開発用の資格情報にCloudFormationの変更権限を渡さないのが、最も確実です。
フックの例です。bash -cやフルパス経由でsam deployを呼ぶ形だけを止め、通常の書き方はaskに任せます。
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": ".claude/hooks/block-sam-deploy.sh"
}
]
}
]
}
}#!/bin/bash
cmd=$(jq -r '.tool_input.command')
if echo "$cmd" | grep -Eq '(^|[[:space:];&|])/[^[:space:]]*/sam[[:space:]]+deploy|(sh|bash) -c .*sam[[:space:]]+deploy'; then
echo "sam deployは通常の書き方で実行してください。" >&2
exit 2
fi
exit 0jqで標準入力のJSONからtool_input.commandを取り出し、該当すれば終了コード2で止めます。この形は迂回だけを狙うので、通常のsam deployはaskの確認ダイアログまで進みます。
同じ考え方でTerraformのapplyを止める手順は、Claude CodeでTerraformのコードを書く手順にあります。CloudFormationやCDKのコードをMCPで検証する方法は、aws-iac-mcp-serverの使い方が扱っています。
よくあるつまずき
- deployが承認なしで動いた。askを書いた設定ファイルが別のスコープで実行中のプロジェクトに読まれていない、
bypassPermissionsなどの権限モードで実行している、迂回形で呼ばれた、のどれかを疑います。ルールの居場所と有効な内容は/permissionsで確認できます sam deployが変更なしで非ゼロ終了する。変更セットに差分が無いとき、既定では非ゼロの終了コードを返します。--no-fail-on-empty-changesetで挙動を変えられますが、Claudeが失敗と誤解しないよう、CLAUDE.mdにこの挙動を書いておくと混乱しません
まとめ
deployを承認制にしても、samconfig.tomlの値は承認画面に映らず、confirm_changeset = falseはdenyも素通りします。人が読む対象は承認ダイアログの文字列だけでなく、samconfig.tomlの差分と変更セットの要約まで含めると、承認が形だけになりません。