Skip to content
blog.kenichikat.me
Go back

Claude Codeに破壊的なgit操作の前に確認させるhookを作った

Claude Codeに作業を任せているとき、ブランチ削除やforce pushのような破壊的・取り消しにくいgit操作は、実行前に必ず確認してほしいと感じる場面があった。CLAUDE.mdに一言書いておくだけでも効くが、確実性という点では心もとない。Claude Codeにはツール呼び出しの前後にコマンドを差し込めるhookという仕組みがあるので、これを使ってガードレールを作った話。

ガードレールをどこに置くか

「〜する前に確認して」というルールをどこに書くべきか、という点でCLAUDE.mdとhookの違いを整理した。

「絶対に確認なしで実行させたくない」という強制力が欲しいなら、CLAUDE.mdだけでは不十分で、hookで実装するのが確実という判断になった。今回はCLAUDE.mdに注意書きを足しつつ、実効性のあるガードとしてPreToolUseフックも用意することにした。

PreToolUseフックの実装

このリポジトリの.claude/settings.jsonにはすでにBash(git branch*)をallowするルールがあり、これがあるとgit branch -dのような破壊的なサブコマンドも無確認で実行できてしまう。そこで、Bashツールの実行前(PreToolUse)に発火するフックを追加し、破壊的なgitサブコマンドの時だけこのallowルールを上書きして確認を強制するようにした。

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "./.claude/hooks/confirm-destructive-git.sh",
            "if": "Bash(git *)"
          }
        ]
      }
    ]
  }
}

判定スクリプト側では、コマンド文字列を正規表現でチェックし、branch -d/-Dpush -f/--forcereset --hardclean -f*のいずれかにマッチしたらpermissionDecision: "ask"を返す。

#!/bin/bash
set -euo pipefail

input=$(cat)
cmd=$(echo "$input" | jq -r '.tool_input.command // empty')

[ -z "$cmd" ] && exit 0

if echo "$cmd" | grep -qE '(^|[;&|]|&&)[[:space:]]*git[[:space:]].*(branch[[:space:]]+(-[a-zA-Z]*[dD][a-zA-Z]*|--delete)|push[[:space:]]+[^;&|]*(-f([[:space:]]|$)|--force([[:space:]]|$|-with-lease)|--force-with-lease)|reset[[:space:]]+[^;&|]*--hard|clean[[:space:]]+[^;&|]*-[a-zA-Z]*f)'; then
  reason=$(echo "$cmd" | cut -c1-200)
  jq -n --arg reason "破壊的・取り消しにくいgit操作です。実行前に内容を確認してください: $reason" '{
    hookSpecificOutput: {
      hookEventName: "PreToolUse",
      permissionDecision: "ask",
      permissionDecisionReason: $reason
    }
  }'
fi

PreToolUseフックは標準入力でツール呼び出しの情報(tool_input.commandなど)をJSONで受け取り、標準出力にhookSpecificOutput.permissionDecisionを含むJSONを返すことで、許可/確認/拒否を制御できる。何も出力しなければ通常の権限判定(今回で言えばallowルール)に従う。

マッチしないコマンド(git statusgit branch一覧表示、git switch -cなど)は何も出力せずそのまま抜けるので、既存の運用は変えずに破壊的な操作だけをピンポイントで捕まえられる。

動作確認

フック本体はツール呼び出しJSONを標準入力に流すことでオフラインで検証できる。

echo '{"tool_input":{"command":"git branch -d feature/foo"}}' | ./confirm-destructive-git.sh
# → permissionDecision: "ask" が返る

echo '{"tool_input":{"command":"git status"}}' | ./confirm-destructive-git.sh
# → 何も出力しない

branch -d/-Dpush --forcepush -freset --hardclean -fdはすべてaskstatusbranch(一覧)・switch -c・git以外のコマンドはすべて素通りすることを確認した。

まとめ


Share this post on:

Next Post
GitHubのPRマージ戦略:Squash and merge・Merge commit・Rebase mergeの違い