导读: 你有没有遇到过这种情况------Claude 写完代码,你还得手动跑一遍格式化;Claude 执行了
rm -rf,你只能在事后后悔;Claude 跑了一个小时任务,你完全不知道它在干嘛。这些问题,Hooks 全部可以解决。本文是 Claude Code 系列的第四篇,带你彻底搞懂什么是 Hooks,它能做什么,以及真实开发中的五个必备场景。
一个让你恍然大悟的比喻
想象你雇了一个实习生来帮你写代码。
他非常聪明,代码质量不错------但有一个问题:你总需要在他每次提交前亲自检查格式、提醒他跑测试、还要在旁边盯着他别误删重要文件。
如果你能提前给他定一套工作流程:
"每次你改完文件,自动 跑一遍 lint。每次你想删文件,先问我 。每次你完成一个任务,发条消息通知我。"
这就是 Hooks 在 Claude Code 里干的事情。
Hooks 让你不再需要守着 Claude,而是把监督和规则变成系统自动执行的机制。
Hooks 是什么
Hooks 是在 Claude Code 运行周期特定节点自动触发的用户自定义脚本。
更技术性地说:当 Claude Code 执行到某个生命周期事件(比如"即将调用 Bash 工具"),系统会暂停,把当前的上下文信息以 JSON 格式通过 stdin 传给你的脚本,脚本执行完后根据退出码和 stdout 的 JSON 输出来决定是否继续、如何继续。
这套机制的本质是在 Claude 的行动链路中,插入确定性的、不依赖 AI 判断的人类定义逻辑。
| Prompt/CLAUDE.md 约束 | Hooks 约束 | |
|---|---|---|
| 执行方式 | 模型"记住"并遵守 | 系统强制拦截 |
| 可靠性 | 概率性(模型可能忘记) | 确定性(代码必然执行) |
| 适合场景 | 行为风格、输出格式 | 安全规则、自动化流程 |
| 可调试性 | 难以追踪 | 日志清晰,可排查 |
Hooks 的生命周期
Claude Code 的运行可以被分解为一系列事件节点,Hooks 就挂载在这些节点上:
用户输入 → [UserPromptSubmit] → Claude 思考 → 决定用工具
↓
[PreToolUse] → 工具执行 → [PostToolUse] → Claude 继续思考
↓
[Stop] → 返回结果给用户
按节点分类,共有以下几类:
| 事件类型 | 触发时机 | 能否拦截 |
|---|---|---|
SessionStart |
会话开始或恢复时 | 否(只注入上下文) |
UserPromptSubmit |
用户提交 prompt 之前 | 是 |
PreToolUse |
工具调用之前 | 是(最强拦截点) |
PostToolUse |
工具调用成功后 | 否(但可向 Claude 反馈) |
Stop |
Claude 完成一轮回答前 | 是(可强制继续) |
Notification |
系统发出通知时 | 否 |
FileChanged |
被监视的文件发生变化 | 否 |
最核心的三个节点:PreToolUse (事前拦截)、PostToolUse (事后处理)、Stop(完成检查)。
配置在哪里
Hooks 写在 JSON 配置文件里,有两个位置:
~/.claude/settings.json ← 个人全局配置(不进 git)
.claude/settings.json ← 项目级配置(可以进 git,团队共享)
两个文件同时生效,不冲突的配置项会合并。
基础结构如下:
json
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "bash ~/.claude/hooks/guard.sh"
}
]
}
],
"PostToolUse": [...],
"Stop": [...],
"Notification": [...]
}
}
三层嵌套结构:
- 事件名 (
PreToolUse、Stop等) - matcher(匹配哪些工具,支持正则,省略则匹配全部)
- hooks 数组(要执行的脚本列表)
Hook 脚本怎么工作
每个 hook 脚本通过 stdin 收到一个 JSON 对象,包含当前事件的上下文信息。
以 PreToolUse + Bash 工具为例,脚本收到的 stdin 大概是这样:
json
{
"hook_event_name": "PreToolUse",
"tool_name": "Bash",
"tool_use_id": "abc123",
"tool_input": {
"command": "rm -rf /tmp/build",
"description": "Clean build artifacts"
}
}
脚本执行后,通过退出码 和 stdout 的 JSON 来控制 Claude 的行为:
| 退出码 | 含义 |
|---|---|
0 |
正常,解析 stdout 里的 JSON 指令 |
2 |
拦截! stderr 的内容作为原因告知 Claude |
| 其他 | 非阻断性错误,执行继续 |
通过 stdout 输出 JSON,还可以显式告诉 Claude 如何决策:
json
{
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "deny",
"permissionDecisionReason": "生产数据库写操作需要人工确认"
}
}
permissionDecision 可以是 allow、deny、ask、defer。
五个真实场景
场景一:自动格式化,再也不手动跑 Prettier
Claude 写完代码,你总得手动格式化一遍?配上这个,Claude 每次写文件后自动格式化:
json
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "npx prettier --write \"$CLAUDE_TOOL_INPUT_FILE_PATH\"",
"async": true
}
]
}
]
}
}
async: true 让格式化在后台跑,不阻塞 Claude 继续工作。$CLAUDE_TOOL_INPUT_FILE_PATH 是系统自动注入的环境变量,指向刚才修改的文件。
场景二:危险命令防火墙,再也不怕 rm -rf
这是最有价值的 Hook 场景之一。在 PreToolUse 挂一个守卫脚本,检测危险命令模式:
.claude/hooks/guard.sh:
bash
#!/bin/bash
# 从 stdin 读取 JSON
input=$(cat)
command=$(echo "$input" | jq -r '.tool_input.command // ""')
# 检测危险模式
dangerous_patterns=("rm -rf /" "rm -rf ~" "DROP TABLE" "truncate" "> /dev/sda")
for pattern in "${dangerous_patterns[@]}"; do
if echo "$command" | grep -qi "$pattern"; then
echo "检测到高危命令:$pattern" >&2
exit 2
fi
done
exit 0
配置:
json
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "bash ${CLAUDE_PROJECT_DIR}/.claude/hooks/guard.sh",
"timeout": 10
}
]
}
]
}
}
触发时,Claude 会看到 stderr 的原因,并中止操作、告知用户。
场景三:任务完成通知,不用盯着屏幕等
让 Claude 跑复杂任务时,你大可以去做别的事,完成后自动收到通知:
macOS 系统通知:
json
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "osascript -e 'display notification \"Claude 完成任务了\" with title \"Claude Code\" sound name \"Glass\"'"
}
]
}
]
}
}
或者通过 Stop Hook 强制检查测试通过后再停止:
bash
#!/bin/bash
# verify-before-stop.sh
cd $CLAUDE_PROJECT_DIR
# 跑测试
if npm test 2>&1 | grep -q "FAIL"; then
echo "测试未通过,请先修复再结束" >&2
exit 2
fi
exit 0
json
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "bash ${CLAUDE_PROJECT_DIR}/.claude/hooks/verify-before-stop.sh"
}
]
}
]
}
}
当 Claude 想"结束回答"时,系统先跑测试,测试不通过则 Claude 必须继续修复------直到测试绿了才能停。
场景四:全程操作审计日志
在生产相关操作或者合规场景下,你想留下 Claude 执行过哪些命令的完整记录:
json
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "jq -c '{ts: now | todate, cmd: .tool_input.command}' >> /tmp/claude-audit.log",
"async": true
}
]
}
]
}
}
async: true 确保日志写入在后台进行,不影响速度。日志格式清晰:
{"ts":"2026-05-26T10:23:11Z","cmd":"npm run build"}
{"ts":"2026-05-26T10:23:45Z","cmd":"git add ."}
{"ts":"2026-05-26T10:23:46Z","cmd":"git commit -m 'feat: add auth'"}
场景五:代码写完立刻跑 lint,把规范变成强制
告诉 Claude "遵循我们的代码规范"------这是 Prompt 约束,它可能遵守也可能忘记。把 lint 挂进 PostToolUse------这是机制约束,必然执行:
json
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "cd ${CLAUDE_PROJECT_DIR} && npx eslint --fix \"$CLAUDE_TOOL_INPUT_FILE_PATH\" 2>&1 | tail -5",
"timeout": 30
}
]
}
]
}
}
如果 lint 发现无法自动修复的问题,会通过 stderr 把信息反馈给 Claude,Claude 会看到并主动去修复。
Hook 的四种类型
上面的例子都用了 "type": "command",这是最常见的。Claude Code 还支持另外三种:
| 类型 | 作用 | 适用场景 |
|---|---|---|
command |
执行本地 shell 命令 | 绝大多数场景 |
http |
发送 POST 请求到 HTTP 端点 | 团队共享策略、远程服务验证 |
prompt |
让 Claude 模型判断(返回 yes/no) | 语义判断,难以用规则表达的条件 |
agent |
启动子 Agent 做复杂验证 | 需要读文件、搜索代码来判断的场景 |
http 类型特别适合团队场景------把安全策略部署在中央服务器,所有成员的 Claude Code 都通过这个端点校验,确保整个团队执行一致的规则:
json
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "http",
"url": "http://localhost:8080/hooks/pre-tool-use",
"timeout": 10,
"headers": { "Authorization": "Bearer $TEAM_TOKEN" },
"allowedEnvVars": ["TEAM_TOKEN"]
}
]
}
]
}
}
一些实用细节
matcher 支持正则模式,可以精准匹配:
json
"matcher": "Bash(git *)" // 只匹配 git 相关命令
"matcher": "Write|Edit" // 匹配写文件或编辑
"matcher": "mcp__*" // 匹配所有 MCP 工具
省略 matcher 则匹配所有工具调用。
路径变量,在 hook 命令里可以直接用:
$CLAUDE_PROJECT_DIR 项目根目录
$CLAUDE_TOOL_INPUT_FILE_PATH 刚才操作的文件路径(Write/Edit 有效)
临时关闭所有 Hooks,不想删配置但临时禁用:
json
{
"disableAllHooks": true,
"hooks": { ... }
}
超时控制,防止 hook 脚本卡住:
json
{
"type": "command",
"command": "bash slow-check.sh",
"timeout": 30
}
默认超时 60 秒,UserPromptSubmit 事件默认 30 秒。
配置一个完整的项目 Hooks
把上面几个场景组合起来,一个典型项目的 .claude/settings.json 长这样:
json
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "bash ${CLAUDE_PROJECT_DIR}/.claude/hooks/guard.sh",
"timeout": 10,
"statusMessage": "安全检查中..."
},
{
"type": "command",
"command": "jq -c '{ts: now | todate, cmd: .tool_input.command}' >> /tmp/claude-audit.log",
"async": true
}
]
}
],
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "npx prettier --write \"$CLAUDE_TOOL_INPUT_FILE_PATH\" 2>/dev/null",
"async": true
}
]
}
],
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "osascript -e 'display notification \"任务完成\" with title \"Claude Code\"'"
}
]
}
]
}
}
这套配置实现了:每次 Bash 命令先过安全检查 + 写审计日志,每次文件修改自动格式化,任务完成发桌面通知。
从 Prompt 约束到机制约束的思维转变
这是 Hooks 背后最重要的工程理念:
告诉 Claude "不要删数据库表" ← Prompt 约束,概率性服从
在 PreToolUse 里加一个拦截脚本 ← 机制约束,确定性执行
前者依赖 Claude 在执行时"记得"这条规则,后者是在系统层面物理拦截------Claude 根本没有机会犯这个错误。
这和 Harness Engineering 的核心思想完全一致(参见本系列第一篇):把"概率性合规"变成"确定性强制"。Claude 越强大、做的事越复杂,你就越需要一套 Hooks 来兜底。
| 适合用 Prompt 约束的 | 适合用 Hooks 约束的 |
|---|---|
| 输出语言风格 | 禁止执行的命令 |
| 代码注释密度 | 文件写入后的格式化 |
| 回答结构偏好 | 任务完成后的测试验证 |
| 对话态度 | 操作日志记录 |
总结
Hooks 是 Claude Code 里最被低估的功能之一。它让你从"每次都要守着 Claude 操作"升级到"把监督规则提前固化进系统"。
四个核心收益:
- 安全保障:危险命令物理拦截,不依赖 AI 的自我约束
- 自动化流程:格式化、测试、通知,全部免手动
- 操作可审计:完整的执行日志,生产环境合规必备
- 完成验证:任务没做完就不让 Claude 停,强制质量把关
配置很简单,门槛极低------一个 JSON 文件 + 几行 Shell 脚本就能开始。
从今天起,把你最频繁手动执行的那个操作,写成第一个 Hook。
AI 相关资源
整理了一些关于 AI 学习资料,持续更新中,希望能帮到大家更好地学习 AI:
加粗样式