跳转到主内容Hooks:让 Claude Code 自动执行你定义的任务
上一篇我们讲了子智能体,把复杂任务拆给多个 AI 并行处理。这一篇讲另一个进阶能力:Hooks。
一句话概括:Hooks 是你预先写好的脚本,在 Claude Code 执行特定操作时自动触发。
Hooks 是什么
先说清楚一件事:Hooks 不是 AI 生成的代码,是你自己写的确定性命令。
Claude Code 在工作过程中会经历很多步骤——读文件、写文件、执行命令、结束会话。Hooks 让你在这些步骤的前后插入自己的脚本。比如:
- 每次 Claude 编辑完
.ts 文件,自动跑一下 Prettier 格式化
- 每次 Claude 准备提交代码,先检查有没有遗留的
console.log
- 会话结束时,自动把本次对话摘要保存到文件
这些操作是固定的、可预测的,不需要 AI 来决定做不做——只要条件满足就执行。
你可以把它类比成 Git Hooks。Git 有 pre-commit、post-merge 这些钩子,Claude Code 也有自己的一套。
触发时机
Claude Code 提供了几个关键的 Hook 事件:
| 事件 | 触发时机 | 典型用途 |
|---|
PreToolUse | 工具调用之前 | 拦截或修改即将执行的操作 |
PostToolUse | 工具调用之后 | 对操作结果做后处理 |
Notification | Claude Code 发出通知时 | 转发通知到其他渠道 |
Stop | 会话结束时 | 保存摘要、清理临时文件 |
SubAgentStop | 子智能体结束时 | 对子智能体的输出做后处理 |
其中最常用的是 PreToolUse 和 PostToolUse。
工具名称是匹配的关键。Claude Code 内部的每个操作都对应一个工具名:
| 工具名 | 对应操作 |
|---|
Write | 写入/创建文件 |
Edit | 编辑文件 |
Bash | 执行终端命令 |
Read | 读取文件 |
比如你想在「每次写文件之后」做点什么,就监听 PostToolUse 事件,匹配 Write 工具。
配置方式
Hooks 在 settings.json 中配置。有两个层级:
- 用户级:
~/.claude/settings.json,对所有项目生效
- 项目级:
项目根目录/.claude/settings.json,仅当前项目生效
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write",
"hooks": [
{
"type": "command",
"command": "prettier --write \"$FILE_PATH\""
}
]
}
]
}
}
- 顶层 key 是事件名(
PostToolUse、PreToolUse 等)
- 每个事件下是一个规则数组,每条规则包含:
matcher:匹配条件,决定哪些工具调用会触发
hooks:要执行的命令列表
- 每个 hook 命令需要指定
type(目前只有 "command")和 command(具体的 shell 命令)
matcher 匹配规则
matcher 决定了 Hook 在什么条件下触发,匹配的是工具名称。
省略 matcher 表示匹配所有工具调用(调试时有用,平时别这么干)。
想同时匹配 Write 和 Edit?写两条规则即可。
环境变量和 stdin
Hook 脚本执行时,Claude Code 会通过环境变量传递上下文信息:
| 变量 | 说明 |
|---|
$TOOL_NAME | 触发的工具名称 |
$FILE_PATH | 操作的文件路径(文件相关工具才有) |
除了环境变量,Hook 脚本还会通过 stdin 收到一个 JSON 对象,包含更详细的工具调用信息。你可以在脚本中读取:
INPUT=$(cat -)
FILE=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')
stdout 和 stderr 的含义
这是 Hooks 设计中一个精巧的地方:脚本输出到 stdout 和 stderr 的内容会被 Claude Code 解读。
| 输出目标 | 作用 |
|---|
| stdout | 内容会作为反馈传递给 Claude(AI 能看到) |
| stderr | 内容会显示给用户(你能在终端看到) |
| 退出码 | 0 表示成功,非零表示失败 |
PreToolUse Hook 退出码非零时,Claude Code 会阻止这次工具调用,并把 stdout 的内容告诉 AI,让它知道为什么被拦了。
实际示例
编辑文件后自动格式化
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write",
"hooks": [{ "type": "command", "command": "npx prettier --write \"$FILE_PATH\" 2>/dev/null || true" }]
},
{
"matcher": "Edit",
"hooks": [{ "type": "command", "command": "npx prettier --write \"$FILE_PATH\" 2>/dev/null || true" }]
}
]
}
}
末尾的 || true 确保即使 Prettier 报错(比如文件类型不支持),Hook 也不会阻断 Claude 的工作流。
保护关键文件不被修改
#!/bin/bash
# scripts/protect-files.sh
INPUT=$(cat -)
FILE=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')
PROTECTED_PATTERNS=("*.env*" "package-lock.json" "pnpm-lock.yaml")
for pattern in "${PROTECTED_PATTERNS[@]}"; do
if [[ "$FILE" == $pattern ]]; then
echo "文件 $FILE 受保护,不允许自动修改。"
exit 1
fi
done
配置 PreToolUse 匹配 Write 和 Edit,指向这个脚本。
提交前检查 console.log
#!/bin/bash
INPUT=$(cat -)
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty')
if [[ "$COMMAND" == *"git commit"* ]]; then
FOUND=$(git diff --cached -- '*.ts' '*.tsx' | grep -c 'console\.log' || true)
if [ "$FOUND" -gt 0 ]; then
echo "暂存区发现 $FOUND 处 console.log,请清理后再提交"
exit 1
fi
fi
会话结束时保存摘要
{
"hooks": {
"Stop": [
{
"hooks": [{ "type": "command", "command": "bash scripts/save-session-summary.sh" }]
}
]
}
}
Stop 事件不需要 matcher,因为它不关联特定工具。
注意事项
Hook 是同步阻塞的。 脚本执行期间 Claude Code 会等待。不要在 Hook 里跑耗时操作。
报错会影响流程。 PreToolUse 返回非零退出码会阻止工具执行,PostToolUse 返回非零退出码会让 Claude 感知到出了问题。给脚本加 || true 可以避免意外中断——除非你就是要阻止操作。
调试方法: 用 echo "DEBUG: ..." >&2 输出调试信息到终端;把 stdin dump 到文件查看;先手动测试脚本再配到 Hook 里。
和 CLAUDE.md 的区别: CLAUDE.md 是「建议」(AI 可能忘记),Hooks 是「规则」(100% 执行)。需要绝对可靠的操作用 Hooks,需要灵活判断的写 CLAUDE.md。
下一篇讲 MCP——让 Claude Code 连接外部工具和服务,从浏览器自动化到数据库查询。
本篇要点:
- Hooks 是确定性脚本,不是 AI 生成的——条件满足就执行
- 最常用的事件:
PreToolUse(拦截)和 PostToolUse(后处理)
- 通过
matcher 匹配工具名称决定触发条件
- stdout 给 AI 看,stderr 给你看,退出码决定是否阻止操作
- 最值得配的:自动格式化、文件保护、会话日志