免费

用 Hooks 管理 git 工作流:让 Claude 提交代码也守规矩

用 PreToolUse Hook 拦截 Bash 里的 git 命令,防止 Claude 直接提交到 main、乱写 commit message、执行危险操作。


Claude Code 有权限直接跑 git commitgit pushgit checkout。大多数时候这很方便,但也意味着它可能:

  • 提交到 main 分支
  • 写出格式随意的 commit message
  • 漏掉 co-author 署名
  • 在不该的时候 force push

Hooks 可以在这些操作发生前介入——拦截、校验、强制符合规范,或者直接阻止危险操作。


思路:用 PreToolUse 拦截 Bash 里的 git 命令

git 操作在 Claude Code 里都是通过 Bash 工具执行的。所以拦截点是:

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

Hook 通过 stdin 拿到完整的命令字符串,在里面检查是否包含特定的 git 操作,然后决定放行还是拦截。

基本模式:

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

# 检查是否是 git commit
if echo "$cmd" | grep -qE '^git commit'; then
  # 做检查...
  echo "不符合规范" >&2
  exit 2  # 阻断并告知 Claude
fi

实战一:禁止直接提交到 main

最常见的需求。Claude 应该在 feature 分支上工作,不应该直接改 main。

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

# 只处理 git commit
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

Claude 收到这个提示后,会主动切换到合适的分支再继续。


实战二:强制 commit message 格式

很多团队用约定式提交(Conventional Commits):feat: xxxfix: xxxdocs: xxx。但 Claude 写的 message 格式不一定符合你的规范。

用 Hook 在提交前校验:

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

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

# 提取 -m 后面的 message
msg=$(echo "$cmd" | grep -oP '(?<=-m )["\x27].*?["\x27]' | tr -d '"'"'" || true)

if [[ -z "$msg" ]]; then
  exit 0  # 没有 -m,可能是用编辑器,跳过
fi

# 检查是否符合 Conventional Commits 格式
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

Claude 收到反馈后会重新构造符合格式的 commit message。


实战三:自动补充 Co-Authored-By

你想在每次 Claude 提交时自动带上署名,记录是 AI 参与的提交。但不想每次手写。

这个用 PostToolUse 更合适——在 commit 完成后检查是否包含署名,没有的话提示 Claude 用 --amend 补上:

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

# 只处理 git commit(排除 --amend 避免循环)
echo "$cmd" | grep -qE '^git commit' || exit 0
echo "$cmd" | grep -q 'amend' && exit 0

# 检查最新 commit 是否有 Co-Authored-By
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

实战四:阻止危险的 git 操作

有些命令一旦执行就很难恢复:

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

# 阻止 force push 到主分支
if echo "$cmd" | grep -qE 'git push.*--force(-with-lease)?.*\b(main|master)\b'; then
  echo "禁止 force push 到 main/master 分支。" >&2
  exit 2
fi

# 阻止 reset --hard(需要显式确认)
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

第二条规则里有个小技巧:要求 Claude 在命令里加 # ALLOW: 注释来"解锁"操作,并说明原因。这样既有保护,又不会完全死锁。


实战五:git 操作审计日志

记录 Claude 执行过的所有 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

这个用 PostToolUse 放在检查之后,只记录实际执行的命令。


组合配置:完整的 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")

# 1. 禁止在 main/master 上 commit 或 push
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

# 2. commit message 格式校验
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
    echo "示例:fix(api): handle null response from upstream" >&2
    exit 2
  fi
fi

# 3. 禁止危险操作(除非有 ALLOW 注释)
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

settings.json

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "bash .claude/hooks/git-guard.sh"
          }
        ]
      }
    ],
    "PostToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "bash -c 'cmd=$(echo \"$CLAUDE_TOOL_INPUT\" | jq -r .command); echo \"$cmd\" | grep -q \"^git\" && echo \"$(date +\\'%Y-%m-%d %H:%M:%S\\') $cmd\" >> ~/.claude/git-audit.log || true'"
          }
        ]
      }
    ]
  }
}

几个注意事项

matcher 要用 Bash,不是 Git

Claude Code 没有专门的 Git 工具,git 命令都通过 Bash 执行。所以 matcher 必须是 "Bash",不能是 "Git"

命令解析不要过度依赖正则

git 命令的写法很多:git commit -m "msg"git commit --message="msg"GIT_AUTHOR_NAME=xxx git commit 等。正则写太死会漏掉边缘情况。建议只拦截最常见的危险模式,其他的相信 Claude 的判断。

全局 vs 项目级

保护 main 分支、格式校验这类规则建议放项目级.claude/settings.json),因为不同项目的主分支名、commit 格式可能不同。审计日志建议放全局,统一记到 ~/.claude/git-audit.log


小结

用 Hooks 管理 git 工作流的关键在于:PreToolUse + Bash matcher + git 命令检测

三个最值得配的规则:
1. 禁止在 main 分支直接提交
2. 校验 commit message 格式
3. 拦截 force push、reset --hard 等危险操作

配合审计日志,Claude 的每一次 git 操作都可追溯。规则从简单的开始,跑通一个再加下一个。