Claude Media
Claude CodeでFlywayのマイグレーションを書く — infoとvalidateで確認しcleanは拒否

Claude CodeでFlywayのマイグレーションを書く — infoとvalidateで確認しcleanは拒否

Claude CodeにFlywayのSQLマイグレーションを書かせるとき、infoとvalidateで確認する順序、適用済みSQLを編集させない規約、cleanとrepairをpermissionsで止める設定を示します。

Claude CodeでFlywayのマイグレーションを書く — infoとvalidateで確認しcleanは拒否

Claude CodeにFlywayのマイグレーションを書かせるときに決めておくことは3つです。確認に使うコマンドの順序、適用済みのSQLファイルを書き換えさせない規約、そしてcleanを実行できないようにする設定です。

Flywayのcleanは、設定したスキーマのオブジェクトをすべて削除します。Flywayのドキュメント自身が「本番DBに対して使わないこと」と注意書きを置いているコマンドです。Claude Codeが開発DBで試行錯誤する流れのなかに、このコマンドが紛れ込む余地を残さないのが、この記事の中心です。

Claude Codeに使わせるFlywayコマンドを仕分ける

Flywayのコマンドは、DBの状態を読むだけのもの、履歴に書き込むもの、中身を消すものに分かれます。Claude Codeの権限もこの3段階に合わせると、設定が単純になります。

コマンド何をするかClaude Codeでの扱い
info何をするか全マイグレーションの詳細と状態を表示するClaude Codeでの扱い許可
validate何をするか適用済みと手元のファイルを突き合わせるClaude Codeでの扱い許可
migrate何をするかスキーマを最新バージョンまで進めるClaude Codeでの扱い開発DBに限り許可
repair何をするかスキーマ履歴テーブルを直すClaude Codeでの扱い確認を挟む(ask)
clean何をするか設定したスキーマのオブジェクトをすべて削除するClaude Codeでの扱い拒否(deny)

repairをaskに置く理由は次の節で説明します。cleanの説明は、ドキュメントでは1行です。「設定したスキーマの全オブジェクトを削除する。Flywayが自動作成したスキーマなら、スキーマごと削除する」。事故防止の既定設定はあるものの、「本当にやるつもりだったのかを対話で聞くことはできない」とも書かれています。確認の最後の砦は、Flywayではなく呼び出す側にあります。

適用済みのSQLを編集させない

Flywayのvalidateは、適用済みのマイグレーションと手元で見つかるマイグレーションを比べます。失敗する条件は3つです。

  • 名前・種類・チェックサムに違いがある
  • 適用済みなのに、手元で見つからなくなったバージョンがある
  • 手元にあるのに、まだ適用されていないバージョンがある

チェックサムは、SQLマイグレーションの場合CRC32で、実行時に保存されます。validateは、手元のファイルのチェックサムがDBに記録された値と同じかを確かめます。

つまり、適用済みのV1__first.sqlをClaude Codeが「ついでに」直すと、validateが落ちます。Claude Codeは不具合を見つけると、既存のSQLを直したくなります。ここを防ぐ規約をCLAUDE.mdに置きます。

## マイグレーション(Flyway)
 
- 適用済みのバージョンのSQLは編集しない。直したいときは新しいバージョンのSQLを足す
- 新しいSQLを書いたら、次の順で実行して出力を読み、結果を報告する
  1. flyway info(新しいファイルが Pending で並んでいるか)
  2. flyway migrate(開発DBだけ)
  3. flyway validate
  4. flyway info(新しいファイルが適用済みになっているか)
- flyway clean と flyway repair は実行しない。必要だと思ったら理由を書いて止まる
- 接続先は環境の設定で渡される。本番の設定や接続情報は読まない

最後の一文が効くのは、Claude Codeが「接続できない」と困ったとき、設定ファイルを探して本番の値に行き当たるのを避けるためです。

repairは「検証を通すための道具」になりうる

repairは、履歴テーブルのチェックサムと説明と種類を、手元のファイルに揃え直します。失敗したマイグレーションを履歴から除く、見当たらなくなったマイグレーションを削除済みとして記録する、という動作もします。

先ほどのvalidateが落ちた場面で、repairを打てばvalidateは通ります。履歴の側を書き換えて、編集を後追いで承認することになるためです。検証の意味が消えるので、Claude Codeが勝手に実行しないようaskに置き、人が理由を見てから判断します。

infoとvalidateを往復する検証ループ

順序はinfo、migrate、validate、infoです。validateをmigrateの前に置くと、新しく書いたファイルが「手元にあるが未適用」にあたるため、検証が落ちます。validateは適用後に走らせるものと決めておきます。

flyway info
flyway migrate
flyway validate
flyway info

出力が長くなる場合は、infoの絞り込み用パラメータが使えます。-infoOfStateは、指定した状態のマイグレーションだけを表示します。-infoSinceVersion=1.1のように、特定のバージョン以降へ絞ることもできます。Claude Codeが読むログを短くしたいときに向きます。-migrationIdsを付けると他の出力を抑え、バージョン番号だけをカンマ区切りで返します。どのバージョンが対象かだけを知りたい場面で、読む量をさらに減らせます。

flyway info -infoOfState=pending

ドキュメントの出力例では、infoのJSONに各マイグレーションのstate(例: Pending)とfilepath(例: V1__first.sql、繰り返し適用型のR__repeatable.sql)が含まれます。validateのJSONはvalidationSuccessfulで成否を返し、失敗時の手がかりとしてinvalidMigrationsとerrorDetails、検査した件数を示すvalidateCountも含みます。Claude Codeに結果を読ませるなら、これらの項目で判断するよう指示に書いておくと、判定が文面の印象に依存しません。

cleanをpermissionsでdenyする

ここからが記事の本題です。.claude/settings.jsonに次を置きます。

{
  "permissions": {
    "deny": [
      "Bash(flyway clean*)",
      "Bash(flyway * clean*)",
      "Bash(mvn *flyway:clean*)",
      "Bash(gradle *flywayClean*)",
      "Bash(./gradlew *flywayClean*)"
    ],
    "ask": [
      "Bash(flyway repair*)",
      "Bash(mvn *flyway:repair*)",
      "Bash(gradle *flywayRepair*)",
      "Bash(./gradlew *flywayRepair*)"
    ]
  }
}

cleanの呼び出し方は、ドキュメントに3通り書かれています。コマンドラインのflyway clean、Mavenのmvn flyway:clean、Gradleのgradle flywayCleanです。flyway -url=... cleanのようにオプションを前に置く形もあるため、flyway * clean*も加えています。ラッパー(./gradlew)の分も別に書きます。

評価はdeny、ask、allowの順に行われ、denyに一致した呼び出しは、より狭いallowがあっても止まります。たとえばBash(flyway *)をallowにしたあとでも、上のcleanのdenyは勝ちます。この性質があるので、infoとvalidateとmigrateの許可は広く書けます。

{
  "permissions": {
    "allow": [
      "Bash(flyway info*)",
      "Bash(flyway validate*)",
      "Bash(flyway migrate*)"
    ]
  }
}

cleanが消す範囲と、出力の読み方

cleanが対象にするのは、設定したスキーマの中のオブジェクトです。Flywayがスキーマを自動で作っていた場合は、スキーマそのものも削除されます。対象のスキーマが開発DBの共有スキーマだと、同じDBを使う別の作業も巻き込みます。

ドキュメントに載っている制限事項は1つだけです。SQL Serverでは、ユーザーは削除されません。

実行結果のJSONには、schemasCleaned(中身を消したスキーマ)、schemasDropped(スキーマごと削除したもの)、warningsが含まれ、operationはcleanです。拒否をすり抜けて実行されたときは、この出力で何が消えたかを確かめます。

Mavenのmvn flyway:cleanは、既定のフェーズがpre-integration-testだとドキュメントにあります。pom.xmlでcleanを実行段階に結び付けてある場合、Bashルールでは見えない経路で動く可能性があります。Claude Codeにpom.xmlを編集させる変更は、レビューでflywayの設定部分を見る価値があります。

denyが止められない形

Bashルールがマッチするのは、Claudeが書いたコマンドの文字列です。権限ドキュメントは、Bash(rm *)がrm -rf build/を止める一方、/bin/rm -rf build/やbash -c 'rm -rf build/'は止めないと例示しています。同じことがflyway cleanにも言えます。次のような形は上のルールをすり抜けます。

  • /opt/flyway/flyway cleanのような絶対パスでの起動
  • sh -c 'flyway clean'のようなシェル経由
  • docker runでFlywayのコンテナを起動する形、npx経由の起動

したがってdenyは、Claudeがいつも書く形を止める保険として置きます。確実さが必要なら、コマンド全文を検査するPreToolUseフックを足します。フックはpermissionDecisionにdenyを返せます。

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/block-flyway-clean.sh",
            "args": []
          }
        ]
      }
    ]
  }
}
#!/bin/bash
# .claude/hooks/block-flyway-clean.sh
COMMAND=$(jq -r '.tool_input.command')
 
if echo "$COMMAND" | grep -Eiq 'flyway.*clean|flywayClean'; then
  jq -n '{
    hookSpecificOutput: {
      hookEventName: "PreToolUse",
      permissionDecision: "deny",
      permissionDecisionReason: "flyway clean is blocked. Add a new migration instead."
    }
  }'
else
  exit 0
fi

文字列の部分一致なので、絶対パスやシェル経由でもコマンド全文にflywayとcleanが含まれていれば止まります。flywayとcleanが離れて出てくる無関係なコマンドも巻き込むため、誤検知は許容する設計です。フックを書くときは、permissionDecisionReasonに「代わりに何をするか」まで入れると、Claude Codeが次の手を選びやすくなります。フックの全体像はClaude Code Hooks完全ガイドにあります。

接続先のDBを役割で分ける

cleanを止めても、本番の接続情報が手元にあれば、migrateが本番へ流れます。Flywayのコマンドは、接続先となる環境が設定または引数で渡されて初めて動きます。ドキュメントにも、すべてのパラメータは省略可能だが「対象環境は設定するか渡す必要がある」とあります。

ここで言えることは単純です。Claude Codeが動く端末には、開発DBの環境だけを渡します。本番へのmigrateは、レビュー後にCIのデプロイジョブが実行します。役割ごとの認証情報の分け方は、Alembicを扱ったClaude CodeでAlembicのマイグレーションを書くの「本番DBの権限を分ける」と同じ考え方です。読み取り系だけを許可する線引きは、Claude CodeでOpenTofuを使うでも同じ形で出てきます。

よくあるつまずき

新しいSQLを書いた直後にvalidateが落ちる

未適用のバージョンがあるときのvalidateは失敗します。仕様どおりの動作なので、migrateの後に実行する順序へ直します。CLAUDE.mdの手順に番号を振っておくと、Claude Codeが順序を入れ替えません。

適用済みのSQLを直したら、validateのチェックサム不一致になった

直した内容を新しいバージョンのSQLとして書き直します。repairを打つ判断は、askの確認画面で理由を読んでから行います。

Claude Codeが「DBをリセットしたい」と言い出す

開発中にスキーマが壊れた場面で、Claude Codeはcleanを候補に挙げることがあります。denyで止まったあとは、フックのpermissionDecisionReasonに書いた代替手段(新しいマイグレーションを足す)に誘導します。開発DBを作り直したい場合は、DBそのものを人が作り直し、Claude Codeにはmigrateだけをやり直させます。

まとめ

  • 確認の順序はinfo、migrate、validate、info。validateは適用後に走らせる
  • 適用済みのSQLは編集させず、足すのは新しいバージョンだけにする
  • cleanはdeny、repairはaskに置く。呼び出し方はCLI、Maven、Gradleの3通りを書く
  • denyは文字列の照合で、境界にはならない。全文を見るPreToolUseフックを併用する
  • 本番の接続情報はClaude Codeの環境に置かず、本番へのmigrateはCIに渡す

サンドボックスでDB接続が止まる場合の切り分けはClaude Codeのsandboxから繋がらないにあります。Packerのように別のコマンドをhookで守る例はClaude CodeでPackerのAMI定義を書くが参考になります。

この記事を共有:XはてブLinkedIn