Free

Hooks で git ワークフローを管理する:Claude のコミットにもルールを

PreToolUse Hook で Bash 内の git コマンドを捕捉し、main への直接コミット・commit メッセージ違反・危険な操作を防ぐ。


Claude Codeはgit commitgit pushgit checkoutを直接実行する権限を持っています。ほとんどの場合は便利ですが、次のような問題が起きる可能性もあります:

  • mainブランチへの直接コミット
  • 自由なフォーマットのcommitメッセージ
  • co-author署名の漏れ
  • 不適切なタイミングでのforce push

Hooksはこれらの操作が実行される前に介入できます——インターセプト、検証、規約の強制、または危険な操作のブロック。


アプローチ:PreToolUseでBash内のgitコマンドをインターセプト

Claude CodeのすべてのGit操作はBashツールを通じて実行されます。インターセプトポイントはここです:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [...]
      }
    ]
  }
}

Hookはstdinで完全なコマンド文字列を受け取り、特定のgit操作を含むか確認し、許可かブロックかを決定します。

基本パターン:

#!/bin/bash
input=$(cat)
cmd=$(echo "$input" | jq -r '.tool_input.command')

if echo "$cmd" | grep -qE '^git commit'; then
  echo "規約に違反しています" >&2
  exit 2
fi

ルール1:mainへの直接コミットをブロック

最も一般的な要件。Claudeはfeatureブランチで作業し、mainを直接変更すべきではありません。

#!/bin/bash
input=$(cat)
cmd=$(echo "$input" | jq -r '.tool_input.command')

echo "$cmd" | grep -qE '^git (commit|push)' || exit 0

current_branch=$(git rev-parse --abbrev-ref HEAD 2>/dev/null)

if [[ "$current_branch" == "main" || "$current_branch" == "master" ]]; then
  echo "$current_branchブランチへの直接コミットは禁止です。featureブランチに切り替えてください。" >&2
  exit 2
fi

ルール2:commitメッセージフォーマットの強制

多くのチームはConventional Commits(feat: xxxfix: xxx)を使います。コミット前に検証します:

#!/bin/bash
input=$(cat)
cmd=$(echo "$input" | jq -r '.tool_input.command')

echo "$cmd" | grep -qE '^git commit' || exit 0

msg=$(echo "$cmd" | grep -oP '(?<=-m )["\x27].*?["\x27]' | tr -d '"'"'" || true)

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

if ! echo "$msg" | grep -qE '^(feat|fix|docs|style|refactor|test|chore|perf|ci|build|revert)(\(.+\))?: .{3,}'; then
  echo "commitメッセージのフォーマットが正しくありません。" >&2
  echo "正しいフォーマット:<type>(<scope>): <description>" >&2
  echo "例:feat(auth): add OAuth login" >&2
  echo "使用可能なtype:feat | fix | docs | style | refactor | test | chore | perf | ci | build | revert" >&2
  exit 2
fi

ルール3:Co-Authored-By署名の強制

#!/bin/bash
input=$(cat)
cmd=$(echo "$input" | jq -r '.tool_input.command')

echo "$cmd" | grep -qE '^git commit' || exit 0
echo "$cmd" | grep -q 'amend' && exit 0

last_msg=$(git log -1 --format="%B" 2>/dev/null)
if ! echo "$last_msg" | grep -q 'Co-Authored-By'; then
  echo "Co-Authored-By署名がありません。" >&2
  echo "実行してください:git commit --amend -m \"\$(git log -1 --format='%s')\" --trailer 'Co-Authored-By: Claude Sonnet 4.6 <[email protected]>'" >&2
  exit 2
fi

ルール4:危険なGit操作をブロック

#!/bin/bash
input=$(cat)
cmd=$(echo "$input" | jq -r '.tool_input.command')

if echo "$cmd" | grep -qE 'git push.*--force(-with-lease)?.*\b(main|master)\b'; then
  echo "main/masterへのforce pushは禁止です。" >&2
  exit 2
fi

if echo "$cmd" | grep -qE 'git reset --hard'; then
  echo "git reset --hardは破壊的な操作のためブロックされました。本当に必要な場合は # ALLOW: コメントで理由を説明してください。" >&2
  exit 2
fi

if echo "$cmd" | grep -qE 'git push.*--delete|git push.*:'; then
  echo "リモートブランチの削除は禁止です。GitHub上で手動で行ってください。" >&2
  exit 2
fi

ルール5:Git操作の監査ログ

#!/bin/bash
input=$(cat)
cmd=$(echo "$input" | jq -r '.tool_input.command')

echo "$cmd" | grep -q '^git' || exit 0

echo "$(date '+%Y-%m-%d %H:%M:%S') GIT: $cmd" >> ~/.claude/git-audit.log

完全な設定:Gitワークフロー統合Hook

.claude/hooks/git-guard.sh

#!/bin/bash
set -e

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

[[ -z "$cmd" ]] && exit 0
echo "$cmd" | grep -q '^git' || exit 0

current_branch=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "unknown")

if echo "$cmd" | grep -qE '^git (commit|push)'; then
  if [[ "$current_branch" == "main" || "$current_branch" == "master" ]]; then
    echo "現在$current_branchブランチにいます。直接のコミット・プッシュは禁止です。featureブランチに切り替えてください。" >&2
    exit 2
  fi
fi

if echo "$cmd" | grep -qE '^git commit.*-m'; then
  msg=$(echo "$cmd" | grep -oP '(?<=-m )["\x27][^\x27"]*["\x27]' | tr -d '"'"'" || true)
  if [[ -n "$msg" ]] && ! echo "$msg" | grep -qE '^(feat|fix|docs|style|refactor|test|chore|perf|ci|build|revert)(\(.+\))?: .{3,}'; then
    echo "commitメッセージのフォーマットが正しくありません。" >&2
    echo "フォーマット:<type>(<scope>): <description>" >&2
    exit 2
  fi
fi

if echo "$cmd" | grep -qE 'git push.*--force|git reset --hard|git push.*--delete'; then
  if ! echo "$cmd" | grep -q '# ALLOW:'; then
    echo "危険な操作がブロックされました:$cmd" >&2
    echo "実行するには、コマンドの末尾に # ALLOW: <理由> を追加してください。" >&2
    exit 2
  fi
fi

注意点

matcherにはBashを使う(Gitではない)

Claude CodeにはGit専用ツールがなく、gitコマンドはすべてBashで実行されます。

正規表現に頼りすぎない

gitコマンドには様々な書き方があります。最も一般的な危険パターンだけをインターセプトし、残りはClaudeの判断を信頼しましょう。

グローバル vs プロジェクト単位

mainブランチ保護とフォーマット検証はプロジェクト単位.claude/settings.json)に。監査ログはグローバルに、~/.claude/git-audit.logへ統一して記録します。


まとめ

HooksでGitワークフローを管理する核心:PreToolUse + Bash matcher + gitコマンド検出

最初に設定すべき3つのルール:
1. mainブランチへの直接コミットをブロック
2. commitメッセージフォーマットの検証
3. force push、reset --hardなどの危険な操作をインターセプト

監査ログと組み合わせることで、Claudeのすべてのgit操作を追跡可能にできます。