SAMを使った開発中に起きた不思議な挙動をClaude Codeに調べてもらった

こんにちは、プロダクト開発部コアグループの井上です。

現在ECSでgithub.com/RichardKnop/machineryを使って実行している定時処理をEventBridge + Lambda/ECSをSAMを使って管理する形に置き換えています。 LambdaがあらかたできたのでECSを追加しようとしていたところ不思議な挙動にハマってしまいました*1

構成

環境別のsamconfigにparameters_overrideを設定してsam deploy時に--config-fileで読み込む構成です。

sam
├─ samconfig-dev.yaml
├─ samconfig-stg.yaml
├─ samconfig-stg.yaml
└─ template.yaml
version: 0.1
default:
  deploy:
    parameters:
      stack_name: stack_name
      s3_prefix: prefix
      region: ap-northeast-1
      capabilities: CAPABILITY_IAM CAPABILITY_NAMED_IAM
      # 環境別パラメータ
      parameter_overrides: >
        Environment="dev"
        EnvironmentPrefix="dev-"
        ECSSubnetIds="subnet-00000000000000000,subnet-11111111111111111,subnet-22222222222222222"
        ECSSecurityGroupIds="sg-00000000000000000,sg-11111111111111111"
  validate:
    parameters:
      lint: true
  sync:
    parameters:
      watch: true
  
  local_start_api:
    parameters:
      warm_containers: EAGER

EventBridgeに指定するECSのTask定義ARNのRevisionを:latestに指定したかったのですが、それは使えなかったため、 最新のARNを取得して--parameter-overridesに指定することにしました。*2

LATEST_ECS_TASK_DEF_ARN=$(aws ecs describe-task-definition \
    --task-definition task-name \
    --region "$REGION" \
    --query 'taskDefinition.taskDefinitionArn' \
    --output text 2>/dev/null || echo "")

sam deploy \
    --stack-name "$STACK_NAME" \
    --config-file "samconfig-${ENVIRONMENT}.yaml" \
    --no-confirm-changeset \
    --no-fail-on-empty-changeset \
    --s3-bucket "${S3_BUCKET}"
    --parameter-overrides "ECSTaskDefinitionArn=${LATEST_ECS_TASK_DEF_ARN}"

不思議に見えた挙動

CloudFormationのスタックを確認すると、以下のような状態になっていました
ECSSecurityGroupIdsだけが空になっている
✅ 最新のECSTaskDefinitionARNが反映されている
✅ 軽く調べるとconfig-fileで指定したparameter_overrides--parameter_overridesで指定したものはマージされないと出てくるが、config-fileでparameter_orverridesに指定したECSSecurityGroupIds以外のパラメータは期待通りに反映されている

Claude Codeにaws-sam-cliを調査してもらうことに

デバッグしていると、sam deployの引数に--parameter-overridesを指定していることが原因なことはわかったのですが、気持ちが悪いので https://github.com/aws/aws-sam-cliをクローンしてClaude Codeに調査してもらうことにしました。

プロンプトはclaude.ai上で発生したことを会話しながらArtifactとしてまとめてもらいました。

Claude Code用のプロンプト

ultrathink
次の調査依頼を実行してください。
# SAM CLI `--parameter-overrides` 挙動調査依頼

## 調査背景

AWS SAM CLIの`sam deploy`コマンドにおいて、`--parameter-overrides`オプションの挙動に関する問題が発生しています。ドキュメントでは「コマンドライン引数が設定ファイルの値を完全に上書きする」と記載され
ていますが、実際には予期しない部分的なマージが発生し、特定のパラメータが欠損する現象が確認されています。

## 調査目的

1. SAM CLIの実際のソースコードを解析し、parameter-overridesの処理ロジックを明確にする
2. 問題の根本原因を特定する
3. 再現可能な最小限のテストケースを作成する

## 具体的な調査項目

### 1. ソースコード解析

以下のファイル/関数を重点的に調査してください:

- **aws-sam-cliリポジトリ内の関連ファイル**
  - `samcli/commands/deploy/command.py`
  - `samcli/commands/_utils/options.py`
  - パラメータマージ処理を行っている関数(`merge_parameter_overrides`など)
  - 設定ファイル読み込み処理
  - コマンドライン引数パース処理

### 2. 問題の再現環境

\`\`\`yaml
# samconfig.yaml
version: 0.1
default:
  deploy:
    parameters:
      parameter_overrides: >-
        Param1=value1
        Param2=value2
        ECSSecurityGroupIds=sg-xxxxx
        Param4=value4
\`\`\`

\`\`\`bash
# 実行コマンド
sam deploy \
  --config-file samconfig.yaml \
  --parameter-overrides "ECSTaskDefinitionArn=arn:aws:ecs:..."
\`\`\`

**期待される結果**: すべてのパラメータが正常に適用される
**実際の結果**: `ECSSecurityGroupIds`が欠損する

### 3. 調査して欲しい具体的なポイント

1. **パラメータマージのロジック**
   - samconfig.yamlのparameter_overridesとCLI引数のparameter_overridesがどのように処理されるか
   - マージ、上書き、または別の処理が行われているか
   - 特定のパラメータ名で問題が発生する理由

2. **パーシング処理**
   - スペース区切りのパラメータリストの解析方法
   - 特殊な文字やパターン(例:複数形の"Ids")の処理
   - エスケープ処理やクォート処理の影響

3. **バージョンによる挙動の違い**
   - Issue #1953では、バージョン0.48.0で問題が発生したとの報告あり
   - 現在のバージョンでの実装状況

### 4. テストケースの作成

以下のパターンでテストケースを作成してください:

1. **基本的なマージテスト**
   - samconfig.yamlに複数のパラメータを設定
   - CLIで1つのパラメータを追加
   - 結果の確認

2. **特定のパラメータ名でのテスト**
   - 複数形で終わるパラメータ(〜Ids、〜s)
   - アンダースコアを含むパラメータ
   - 大文字小文字の組み合わせ

3. **エッジケース**
   - 空文字列の値
   - スペースを含む値
   - 特殊文字を含む値

## 調査結果として期待する成果物

1. **問題の根本原因の説明**
   - 該当するソースコードの箇所
   - なぜ特定のパラメータが欠損するのか
   - 実際の処理フローの詳細

2. **再現可能なテストコード**
   - 最小限の再現コード
   - 問題を確実に再現する手順
   - 各パターンでの挙動の違い

3. **技術的な解説**
   - パラメータ処理の内部実装の詳細
   - 問題が発生する条件の明確化
   - ドキュメントと実装の差異

## 参考情報

- [AWS SAM CLI GitHub Repository](https://github.com/aws/aws-sam-cli)
- [Issue #2380: parameter-overrides on command line block samconfig.toml values](https://github.com/aws/aws-sam-cli/issues/2380)
- [Issue #5169: How does sam deploy with '--config-env' and '--parameters-overrides' work](https://github.com/aws/aws-sam-cli/issues/5169)
- [SAM CLI Configuration File Documentation](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-config.html)

## 追加の調査項目(オプション)

時間があれば、以下も調査してください:

1. 他のSAMコマンド(`sam local start-api`など)での同様の問題の有無
2. 環境変数(`SAM_PARAM_*`)の処理ロジック
3. 将来的な改善提案(より良いパラメータ管理方法)

CloudFormationのUsePreviousValue機能

10分程度で調査は終了し、今回の挙動についてあっさり理解することができました。 SAM CLIは、コマンドライン引数で--parameter-overridesが指定されると、config-fileのparameter_overridesは無視され、マージは行われません。なので今回はparameter_overridesにはECSTaskDefinitionArnだけが指定されていました。

今回問題のパラメータ以外が反映されていたのは、CloudFormationのUsePreviousValueという機能によるものでした。 SAMは指定がないParameterにはUsePreviousValue=trueを設定します。

https://github.com/aws/aws-sam-cli/blob/5318a4b97c2672b7376c6363e35c0c486af46170/samcli/commands/deploy/deploy_context.py#L332

    @staticmethod
    def merge_parameters(template_dict: Dict, parameter_overrides: Dict) -> List[Dict]:
        """
        CloudFormation CreateChangeset requires a value for every parameter
        from the template, either specifying a new value or use previous value.
        For convenience, this method will accept new parameter values and
        generates a dict of all parameters in a format that ChangeSet API
        will accept

        :param template_dict:
        :param parameter_overrides:
        :return:
        """
        parameter_values: List[Dict] = []

        if not isinstance(template_dict.get("Parameters", None), dict):
            return parameter_values

        for key, _ in template_dict["Parameters"].items():
            obj = {"ParameterKey": key}

            if key in parameter_overrides:
                obj["ParameterValue"] = parameter_overrides[key]
            else:
                obj["UsePreviousValue"] = True

            parameter_values.append(obj)

        return parameter_values

今回は引数に--parameters-overrideをつける前に問題のパラメータ(ECSSecurityGroupIds)以外の値は設定した状態でsam deployしており、その後にECSSecurityGroupIdsの値を追加したのでECSSecurityGroupIdsだけ反映されないように見えてしまっていたという状況でした。*3

おわりに

LLM登場後も、ほとんど知らない言語・ライブラリのソースコードを問題解決のために見に行くのは私の中では最終手段でしたが、Claude Codeの活用で一気に手軽な手段になったなと感じる出来事でした。
皆さんもClaude Codeで「とりあえずソースコード読んでみる」やってみてください。


*1:実際には結論の通りUsePreviousValueによるものでした。

*2:タスク定義自体をSAMで管理すればそれを参照することもできたはずですが、このプロジェクトではタスク定義やサービス定義はecspressoで管理していたためそれは選択しませんでした。

*3:ECSSecurityGroupIdsも先に値を入力していたら勘違いしたまま気づくのがさらに遅れることになっていたので逆に良かったかもしれません。