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が自律的に修正作業を始めてくれる。
記事が見つかりません:
記事が見つかりません: