Mascot Logo
ai-agents-tutorial

Part 2 · 第 9 课,共 16 课

用钩子 (Hooks) 自动化 Claude Code

事件驱动的自动化流程。一个完整的端到端实战示例。

10 min

第 1 步 / 共 7 步 · 什么是钩子 (Hooks)?

钩子 (Hook) 是当会话中发生特定事件时,Claude Code 自动运行的一条命令——例如在它运行某个工具之前、编辑文件之后、或结束本轮回复时。

钩子把"请记得每次都运行格式化工具"从一句叮嘱变成一种确定的保证。你不再去请求智能体守规矩,而是把一条 Shell 命令绑定到某个事件上,由智能体的运行环境确定性地执行它。对新手最有用的两个事件是:

  • PreToolUse——在工具调用运行之前触发,因此你可以检查它,甚至拦截它(非常适合做安全防护)。
  • PostToolUse——在工具调用成功之后触发,因此你可以对其做出响应(非常适合对 Claude 刚刚编辑过的文件自动格式化)。

第 2 步 / 共 7 步 · 钩子在哪里配置

钩子配置在你的 settings.json 中,而不是某个专门的"钩子文件"里。配置权限或环境变量用的就是这同一批设置文件:

  • ~/.claude/settings.json——对你的所有项目生效(用户级)。
  • .claude/settings.json——只对当前项目生效,可以提交到仓库与团队共享。
  • .claude/settings.local.json——项目级,但会被 git 忽略(用于个人覆盖配置)。

第 3 步 / 共 7 步 · 钩子配置的结构

每一份钩子配置都遵循同样的嵌套结构:一个顶层的 "hooks" 对象,以事件名为键,值是一个匹配器数组。每个匹配器都有一个 "matcher"(它对哪些工具生效)和一个 "hooks" 命令数组:

{
"hooks": {
  "PreToolUse": [
    {
      "matcher": "Bash",
      "hooks": [
        { "type": "command", "command": "./.claude/hooks/guard-bash.sh" }
      ]
    }
  ]
}
}

"matcher" 会与工具名(如 BashEditWrite)进行匹配。用 "Edit|Write" 可同时匹配多个;用 "*"(或空字符串)匹配所有工具。内层 "hooks" 数组中的每一项都会运行一条 "command"——而 Claude Code 会把该事件的 JSON 数据(工具名、输入参数、工作目录等)通过 stdin(标准输入) 传给这条命令。

第 4 步 / 共 7 步 · 事件一——PreToolUse 安全防护

我们来在一条危险命令运行之前就把它拦下。在项目的 .claude/hooks/ 文件夹下创建防护脚本:

mkdir -p .claude/hooks
cat > .claude/hooks/guard-bash.sh <<'EOF'
#!/bin/sh
# 从 stdin 读取事件 JSON 并取出待执行的命令。
command=$(jq -r '.tool_input.command' < /dev/stdin)

case "$command" in
*"rm -rf"*)
  echo "已拦截:钩子不允许执行 'rm -rf'。" >&2
  exit 2  # 退出码 2 告诉 Claude Code 拦截本次工具调用
  ;;
esac
exit 0      # 退出码 0 = 放行,回到正常的权限流程
EOF
chmod +x .claude/hooks/guard-bash.sh

PreToolUse 事件会把待执行的 Bash 命令放在 tool_input.command 中传入。以退出码 2 退出会告诉运行环境拒绝本次调用,并把你的 stderr 信息反馈给 Claude;以 0 退出则让正常的权限流程继续。

第 5 步 / 共 7 步 · 事件二——PostToolUse 自动格式化

现在再加一个在 Claude 编辑文件之后运行的事件,让你的代码始终保持格式整洁。在你的 .claude/settings.json 中为 EditWrite 工具添加一个 PostToolUse 匹配器:

{
"hooks": {
  "PreToolUse": [
    {
      "matcher": "Bash",
      "hooks": [
        { "type": "command", "command": "./.claude/hooks/guard-bash.sh" }
      ]
    }
  ],
  "PostToolUse": [
    {
      "matcher": "Edit|Write",
      "hooks": [
        { "type": "command", "command": "./.claude/hooks/format.sh" }
      ]
    }
  ]
}
}

PostToolUse 事件会把 Claude 改动的文件路径放在 tool_input.file_path 中传入。脚本只格式化那一个文件:

cat > .claude/hooks/format.sh <<'EOF'
#!/bin/sh
file=$(jq -r '.tool_input.file_path' < /dev/stdin)
case "$file" in
*.ts|*.tsx|*.js|*.json) npx prettier --write "$file" ;;
esac
exit 0
EOF
chmod +x .claude/hooks/format.sh

第 6 步 / 共 7 步 · 加餐——Git pre-commit 提交前钩子

Claude Code 的钩子在会话内部触发。但你也可以从一个 Git 钩子里调用 Claude,它会在 git commit 时触发,与任何会话无关。Git 会查找 .git/hooks/pre-commit 脚本,如果它以非零状态退出,提交就会被中止。

关键在于无头模式(headless)claude -p "<指令>" 会以非交互方式运行单条指令并打印结果,非常适合脚本和 CI。

cat > .git/hooks/pre-commit <<'EOF'
#!/bin/sh
# 以无头模式运行 Claude Code,对暂存的文件做一次快速检查。
claude -p "检查暂存的 git 改动中是否有明显的拼写错误或密钥泄露。
只回复 OK 一个词,或用一行说明问题。" \
--allowedTools "Read,Bash(git diff:*)"
EOF
chmod +x .git/hooks/pre-commit

第 7 步 / 共 7 步 · 检查点与本节回顾

本节回顾

  • 钩子 (Hooks) 是 Claude Code 在会话事件上自动运行的命令,配置在 settings.json 中(用户级、项目级或本地级)。
  • PreToolUse 在工具运行前触发;退出码 2 会拦截调用。非常适合做安全防护。
  • PostToolUse 在工具成功后触发;可用它对 Claude 刚改动的文件自动格式化或检查。
  • 配置层层嵌套:"hooks" → 事件名 → "matcher" + "hooks" 数组({ "type": "command", "command": ... }),事件 JSON 会通过命令的 stdin 传入。
  • Git 的 pre-commit 钩子(.git/hooks/)是另一回事——在提交时用 claude -p "<指令>" 做一次非交互检查即可。

常见问题

Claude Code 的钩子在哪里配置?

配置在 settings.json 文件的 "hooks" 键下。用 ~/.claude/settings.json 让钩子对所有项目生效,用 .claude/settings.json 通过仓库与团队共享,用 .claude/settings.local.json 做个人的、会被 git 忽略的覆盖配置。会话内的 /hooks 命令只能查看钩子,无法编辑,所以请直接改 JSON 或让智能体帮你改。

怎样让钩子拦截一条命令?

使用 PreToolUse 钩子。你的 command 从 stdin 读取事件 JSON(对 Bash 来说,待执行命令在 tool_input.command 中),如果你以退出码 2 退出,Claude Code 就会拒绝该工具调用,并把你的 stderr 信息展示给智能体。以 0 退出则放行,并回到正常的权限流程。

PreToolUse 和 PostToolUse 有什么区别?

PreToolUse 在工具调用之前运行,因此可以检查或拦截它——适合做安全防护。PostToolUse 在工具调用成功之后运行,因此可以对结果做出响应——适合对 Claude 刚编辑过的文件自动格式化或检查(文件路径在 tool_input.file_path 中)。

Claude Code 钩子和 Git 钩子是一回事吗?

不是。Claude Code 钩子配置在 settings.json 中,在 Claude Code 会话内部的事件上触发(如 PreToolUsePostToolUse)。Git 钩子位于 .git/hooks/ 中,在 pre-commit 等 Git 事件上触发,与任何会话无关。你可以把两者结合:让 Git 的 pre-commit 钩子用 claude -p "<指令>" 以无头模式调用 Claude。