用 PreToolUse Hook 攔截 Bash 裡的 git 命令,防止 Claude 直接提交到 main、亂寫 commit message、執行危險操作。
Claude Code 有權限直接跑 git commit、git push、git checkout。大多數時候這很方便,但也意味著它可能:
Hooks 可以在這些操作發生前介入——攔截、校驗、強制符合規範,或者直接阻止危險操作。
git 操作在 Claude Code 裡都是透過 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
最常見的需求。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
#!/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 message 格式不符合規範。" >&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
#!/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
#!/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 "禁止 force push 到 main/master 分支。" >&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
#!/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
.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 message 格式不符合規範。" >&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 執行,所以 matcher 必須是 "Bash"。
命令解析不要過度依賴正規表示式
git 命令寫法很多,正規表示式寫太死會漏掉邊緣情況。建議只攔截最常見的危險模式,其他的相信 Claude 的判斷。
全域 vs 專案級
保護 main 分支、格式校驗建議放專案級(.claude/settings.json)。稽核日誌建議放全域,統一記到 ~/.claude/git-audit.log。
用 Hooks 管理 git 工作流的關鍵:PreToolUse + Bash matcher + git 命令偵測。
三個最值得配的規則:
1. 禁止在 main 分支直接提交
2. 校驗 commit message 格式
3. 攔截 force push、reset --hard 等危險操作
配合稽核日誌,Claude 的每一次 git 操作都可追溯。