Claude Code HooksでGitワークフローを自動化する — commit前後の品質ゲートを実装する

Claude Code HooksでGitワークフローを自動化する — commit前後の品質ゲートを実装する

Claude Code HooksをGitワークフローに組み込む実践ガイド。commitトリガーでのlint/test自動実行、危険コマンドのブロック、コミットメッセージの自動生成、git push前のセキュリティチェックなど、実際に動くコード例付きで解説。

エンジニアのゆとです。

Claude CodeのHooksは「Claude Codeが何かをする直前・直後にスクリプトを実行できる仕組み」だ。使い方を解説した記事は増えてきたが、Gitワークフローに特化した使い方はあまり書かれていない。

ここで解決したいのはこういう問題:

  • Claude Codeが git commit しようとするとき、自分のlintルールを強制させたい
  • 型エラーが残ったままcommitされるのを防ぎたい
  • .env や秘密鍵を含むファイルが誤ってcommitされそうになったらブロックしたい
  • commitメッセージを自動生成させたいが、テンプレートに従わせたい

これらをHooksで全部実装できる。ただし「現時点でHooksは git commit というコマンド実行を捕捉できるが、gitのpre-commitフックとは別物」という制約を理解した上で使う必要がある。


前提:Claude Code HooksとGitフックの違い

よく混同されるので先に整理する。

Gitのpre-commitフック(.git/hooks/pre-commit)はgitコマンドのライフサイクルに直接フックする。git commit を打った瞬間に走る。

Claude Code HooksはClaude Codeが発行するツール呼び出しを捕捉する。Claude Codeが Bash(git commit ...) を実行しようとしたときに発火する。

つまり「自分でターミナルから打った git commit」はClaude Code Hooksでは捕捉できない。あくまで「Claude Codeがgitコマンドを打とうとしたとき」が対象だ。

逆に言えば、Claude CodeがGit操作をする限り、この仕組みで確実に品質チェックを挟める。


設定ファイルの場所と基本構造

プロジェクトルートの .claude/settings.json に書く。

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "/path/to/pre-git-check.sh"
          }
        ]
      }
    ],
    "PostToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "/path/to/post-git-notify.sh"
          }
        ]
      }
    ]
  }
}

Hookスクリプトはstdinからツール呼び出しの情報をJSONで受け取る:

{
  "tool_name": "Bash",
  "tool_input": {
    "command": "git commit -m 'feat: add user auth'",
    "description": "Commit the changes"
  }
}

このJSONを解析して、commitコマンドかどうかを判断して処理を分岐させる。


パターン1: commit前のlint + 型チェック強制

commit前に自動でlintとTypeScriptの型チェックを走らせて、失敗したらブロックする。

.claude/hooks/pre-commit-guard.sh:

#!/bin/bash

# stdinからJSONを読み取る
INPUT=$(cat)

# コマンドを取得
COMMAND=$(echo "$INPUT" | python3 -c "
import json, sys
data = json.load(sys.stdin)
print(data.get('tool_input', {}).get('command', ''))
")

# git commit コマンドでなければスキップ
if ! echo "$COMMAND" | grep -qE "^git commit|^git push"; then
  exit 0
fi

echo "品質チェックを実行中..." >&2

# ESLintを実行
if command -v npx &> /dev/null && [ -f ".eslintrc.json" -o -f "eslint.config.js" ]; then
  echo "ESLint..." >&2
  if ! npx eslint src/ --max-warnings=0 2>&1; then
    echo "ESLintエラーが残っています。修正してからcommitしてください。" >&2
    exit 2  # exit 2 でClaudeにブロックを伝える
  fi
fi

# TypeScript型チェック
if command -v npx &> /dev/null && [ -f "tsconfig.json" ]; then
  echo "TypeScript型チェック..." >&2
  if ! npx tsc --noEmit 2>&1; then
    echo "型エラーが残っています。修正してからcommitしてください。" >&2
    exit 2
  fi
fi

echo "品質チェック完了。commitを続行します。" >&2
exit 0

exit 2 を返すとClaude Codeはアクションをブロックして、stderrの内容をClaudeに伝える。Claudeはエラーの内容を把握した上で「修正が必要です」と伝えてくれる。

settings.jsonへの追加:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "/Users/yourname/your-project/.claude/hooks/pre-commit-guard.sh"
          }
        ]
      }
    ]
  }
}

パターン2: 危険なファイルのcommit防止

.env、秘密鍵、大容量ファイルが誤ってstagingに入っていたらblockする。

.claude/hooks/secret-guard.sh:

#!/bin/bash

INPUT=$(cat)
COMMAND=$(echo "$INPUT" | python3 -c "
import json, sys
data = json.load(sys.stdin)
print(data.get('tool_input', {}).get('command', ''))
")

# git commit または git add のみ対象
if ! echo "$COMMAND" | grep -qE "^git (commit|add)"; then
  exit 0
fi

# ステージングに危険なファイルが含まれていないか確認
STAGED_FILES=$(git diff --cached --name-only 2>/dev/null)

if [ -z "$STAGED_FILES" ]; then
  exit 0
fi

BLOCKED=0
BLOCKED_FILES=""

while IFS= read -r file; do
  # .env系のファイル
  if echo "$file" | grep -qE "\.env($|\.)" ; then
    BLOCKED_FILES="$BLOCKED_FILES\n  ⚠️  $file (.envファイル)"
    BLOCKED=1
  fi

  # 秘密鍵ファイル
  if echo "$file" | grep -qE "\.(pem|key|p12|pfx)$"; then
    BLOCKED_FILES="$BLOCKED_FILES\n  ⚠️  $file (秘密鍵ファイル)"
    BLOCKED=1
  fi

  # 100MB超のファイル
  if [ -f "$file" ]; then
    SIZE=$(stat -f%z "$file" 2>/dev/null || stat -c%s "$file" 2>/dev/null)
    if [ -n "$SIZE" ] && [ "$SIZE" -gt 104857600 ]; then
      BLOCKED_FILES="$BLOCKED_FILES\n  ⚠️  $file (ファイルサイズ超過: ${SIZE}バイト)"
      BLOCKED=1
    fi
  fi
done <<< "$STAGED_FILES"

if [ "$BLOCKED" -eq 1 ]; then
  echo "以下の危険なファイルがstageされています:" >&2
  echo -e "$BLOCKED_FILES" >&2
  echo "" >&2
  echo ".gitignoreに追加するか、git rm --cached でアンステージしてください。" >&2
  exit 2
fi

exit 0

gitのpre-commitフックと二重にやることになるが、「Claude Codeが操作するとき」という層でも守っておくと安心だ。


パターン3: commitメッセージのフォーマット検証

Conventional Commits(feat: fix: docs: 等)を強制させる。

.claude/hooks/commit-message-check.sh:

#!/bin/bash

INPUT=$(cat)
COMMAND=$(echo "$INPUT" | python3 -c "
import json, sys
data = json.load(sys.stdin)
print(data.get('tool_input', {}).get('command', ''))
")

# git commit コマンドのみ対象
if ! echo "$COMMAND" | grep -q "^git commit"; then
  exit 0
fi

# コマンドからメッセージを抽出(-m オプションの値)
MESSAGE=$(echo "$COMMAND" | python3 -c "
import re, sys
cmd = sys.stdin.read()
match = re.search(r\"-m ['\\\"](.+?)['\\\"]|(-m )(\S+)\", cmd)
if match:
    print(match.group(1) or match.group(3))
" 2>/dev/null)

if [ -z "$MESSAGE" ]; then
  exit 0  # メッセージが取れなければスキップ
fi

# Conventional Commitsのプレフィックスを確認
VALID_PREFIXES="feat|fix|docs|style|refactor|test|chore|perf|build|ci|revert"
if ! echo "$MESSAGE" | grep -qE "^($VALID_PREFIXES)(\(.+\))?!?: .+"; then
  echo "commitメッセージがConventional Commitsの形式に沿っていません。" >&2
  echo "" >&2
  echo "期待する形式: <type>(<scope>): <description>" >&2
  echo "例: feat(auth): add OAuth2 login" >&2
  echo "    fix: resolve null pointer in user service" >&2
  echo "" >&2
  echo "使えるtype: $VALID_PREFIXES" >&2
  exit 2
fi

exit 0

パターン4: git push前のリモートブランチ確認

mainブランチへの直接pushをブロックする(force pushも含む)。

.claude/hooks/push-guard.sh:

#!/bin/bash

INPUT=$(cat)
COMMAND=$(echo "$INPUT" | python3 -c "
import json, sys
data = json.load(sys.stdin)
print(data.get('tool_input', {}).get('command', ''))
")

# git push コマンドのみ対象
if ! echo "$COMMAND" | grep -q "^git push"; then
  exit 0
fi

# force pushのブロック
if echo "$COMMAND" | grep -qE "\-\-force|\-f "; then
  echo "force pushは禁止されています。" >&2
  echo "本当に必要な場合は手動で実行してください。" >&2
  exit 2
fi

# mainブランチへの直接pushのブロック
CURRENT_BRANCH=$(git branch --show-current 2>/dev/null)
if [ "$CURRENT_BRANCH" = "main" ] || [ "$CURRENT_BRANCH" = "master" ]; then
  echo "mainブランチへの直接pushはブロックされています。" >&2
  echo "プルリクエストを作成してください。" >&2
  exit 2
fi

exit 0

パターン5: commit後の自動通知

commit成功後にデスクトップ通知を飛ばす(Macの場合)。

.claude/hooks/post-commit-notify.sh:

#!/bin/bash

INPUT=$(cat)
COMMAND=$(echo "$INPUT" | python3 -c "
import json, sys
data = json.load(sys.stdin)
print(data.get('tool_input', {}).get('command', ''))
")

# PostToolUse用なので、exit codeが0(成功)の場合のみ通知したい
# ただしPostToolUseはツール実行後に呼ばれるため、成功前提でOK

if ! echo "$COMMAND" | grep -q "^git commit"; then
  exit 0
fi

# commitメッセージを取得
LAST_COMMIT=$(git log -1 --format="%s" 2>/dev/null)

# macOSのosascriptで通知
osascript -e "display notification \"${LAST_COMMIT}\" with title \"Claude Code: Commit完了\"" 2>/dev/null || true

exit 0

PostToolUseに設定する(PreToolUseではなく):

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "/path/to/post-commit-notify.sh"
          }
        ]
      }
    ]
  }
}

全パターンをまとめたsettings.json

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "/path/to/.claude/hooks/pre-commit-guard.sh"
          },
          {
            "type": "command",
            "command": "/path/to/.claude/hooks/secret-guard.sh"
          },
          {
            "type": "command",
            "command": "/path/to/.claude/hooks/commit-message-check.sh"
          },
          {
            "type": "command",
            "command": "/path/to/.claude/hooks/push-guard.sh"
          }
        ]
      }
    ],
    "PostToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "/path/to/.claude/hooks/post-commit-notify.sh"
          }
        ]
      }
    ]
  }
}

複数のhookスクリプトを並べると、順番に実行される。どれか一つでも exit 2 を返したらブロックされる。


現時点での制約と回避策

制約1: 専用の PreCommit / PostCommit イベントが存在しない

GitHub IssueにPreCommit/PostCommitフックの追加要望が出ているが、2026年4月時点では未実装。

現在の回避策は「PreToolUseでBashを捕捉してgit commitを検出する」方法。これで実質的に同じ動作を実現できる。

制約2: Bashコマンド以外のGit操作は捕捉できない

Claude Codeが内部的にGitAPIを使っている場合(Bashコマンド経由でない場合)は捕捉できない。ただし現時点では大半のGit操作はBashコマンド経由なので、実用上は問題ない。

制約3: 自分がターミナルから打ったgitコマンドには効かない

繰り返しになるが、Claude Code Hooksは「Claude Codeが発行するコマンド」に対してのみ動作する。Gitの本来のpre-commitフック(.git/hooks/pre-commit)と併用するのが現実的だ。


まとめ

Claude Code HooksでGitワークフローを自動化する主なパターン:

  • PreToolUse + Bashマッチャーで git commit を捕捉してlint/型チェックを強制
  • 危険なファイル(.env、秘密鍵)のcommitをブロック
  • commitメッセージのフォーマット(Conventional Commits)を強制
  • mainへの直接push・force pushをブロック
  • PostToolUseでcommit完了通知

「Claude Codeが勝手にcommitする前に確認させたい」というニーズには、exit 2によるブロックが有効だ。スクリプトがstderrに出力した内容はClaudeに伝わるので、「なぜブロックしたか」の理由説明を書いておくと、Claudeが自律的に修正作業を始めてくれる。

記事が見つかりません:

記事が見つかりません:

code.claude.com
← 記事一覧に戻る