在 AI 编程助手日益普及的今天,Claude Code 凭借其强大的上下文理解能力成为开发者利器。然而,大语言模型的概率性本质意味着它无法保证每次行为都符合预期。Hooks 系统正是为解决这一痛点而生——它为 Claude 提供了确定性执行层,让你可以在 AI 行动前后插入可靠的逻辑检查。本文将深入剖析 Hooks 的设计哲学、事件模型与实战技巧,帮助你构建更安全、更高效的自动化工作流。
1. Hooks 设计哲学:概率世界的确定性护栏
Claude 是概率性的。你让它\"提交前跑一下 lint\",它大概率会做,但不是每次都做。你让它\"不要碰 .env 文件\",它大部分时候听话,但偶尔还是会手滑。这不是 Claude 的 bug,这是大语言模型的本质——它基于概率生成输出,没有任何硬性保证。

Hooks 就是用来填这个缺口的。它是 Claude Code 的确定性执行层——你定义一个规则,每次触发条件满足时,它 100% 执行,没有\"大概率\"、\"通常\"这种模糊地带。
核心设计原则:
- 确定性:同样的输入,永远产生同样的行为
- 可组合:多个 hook 可以叠加在同一个事件上,按顺序执行
- 可测试:hook 本质上就是 shell 命令或 HTTP 请求,可在终端单独测试
- 语言无关:支持 JavaScript、Python、Bash、Java、TypeScript、C++ 等任何可执行脚本
Hooks vs Skills 的职责边界:
- Skills = 教 Claude 新能力(扩展知识面)
- Hooks = 在 Claude 行动前后插入确定性逻辑(加一道关卡)
简单说:Skills 是给 Claude 的教材,Hooks 是给 Claude 的护栏。
整体架构: Hooks 的执行流程是一条清晰的管道:
事件触发 → 匹配器过滤 → 条件判断 → 钩子执行 → 反馈注入
比如 Claude 准备执行一条 Bash 命令(事件触发),hook 系统检查命令名是否匹配 (匹配器过滤),再检查命令内容是否包含 Bash(条件判断),如果都命中就执行你定义的检查脚本(钩子执行),脚本的输出会反馈给 Claude 决定下一步行动(反馈注入)。git push
2. 事件模型完全参考:精准定位触发时机
Hooks 的触发点覆盖了 Claude Code 的整个生命周期。每种事件对应一个特定的时机,你需要根据自己的需求选择合适的挂载点。
UserPromptSubmit — 用户输入拦截
用户按下回车提交 prompt 之后、Claude 开始处理之前触发。
典型用途: 输入增强、敏感信息过滤、输入规范化
PreToolUse — 工具执行前拦截(最常用)
Claude 决定调用某个工具之后、实际执行之前触发。
典型用途: 阻止危险操作(、rm -rf /)、校验工具参数、实现审批流程git push --force
PostToolUse — 工具执行后反应
工具执行完成之后触发。
典型用途: 自动格式化(写完文件后跑 Prettier/Black)、日志记录、注入额外上下文
是个很有用的机制——hook 的输出可以作为额外信息注入到 Claude 的上下文中。比如写完代码后跑了 lint,把 lint 结果通过 additionalContext 告诉 Claude,它就能自动修复问题。additionalContext
Stop — 响应完成检查
Claude 认为自己已经完成任务、准备结束回复时触发。
典型用途: 完成度验证、质量门禁、自动追加操作
CwdChanged — 工作目录变更(v2.1.83+)
Claude Code 的工作目录发生变化时触发。
典型用途: 切换目录后自动加载该目录的配置
TaskCreated — 任务创建事件(v2.1.84+)
使用 参数或通过子代理创建新任务时触发。--task
典型用途: 任务级别的初始化操作
WorktreeCreate — Worktree 创建事件(v2.1.84+)
Claude Code 创建 Git worktree 时触发。
典型用途: worktree 创建后自动安装依赖
PreCompact / PostCompact — 上下文压缩生命周期
当 Claude Code 进行上下文压缩时触发。
典型用途: 在压缩前保存关键信息,压缩后注入必须保留的上下文
3. 钩子类型详解:灵活的执行方式
事件决定了\"什么时候触发\",钩子类型决定了\"触发后做什么\"。Claude Code 支持五种钩子类型,覆盖了从简单脚本到复杂工作流的各种场景。

command — 执行本地 shell 命令
最常用的类型。直接在本地执行一条 shell 命令,通过退出码和 stdout 返回结果。
{
"type"
:
"command"
,
"command"
:
"python3 /path/to/check_script.py"
}
hook 执行时,Claude Code 会注入一组环境变量供脚本使用,包括 (项目根目录)、$CLAUDE_PROJECT_DIR(涉及文件路径)、$CLAUDE_FILE_PATH(工具名)等。$CLAUDE_TOOL_NAME 参数控制超时时间(毫秒),默认 60 秒:timeout
{
"type"
:
"command"
,
"command"
:
"npm test"
,
"timeout"
:
300000
}
http — POST 到 webhook 端点
把事件数据以 JSON 格式 POST 到一个 HTTP 端点。适合做外部集成。
{
"type"
:
"http"
,
"url"
:
"https://hooks.slack.com/services/T00000/B00000/XXXX"
}
典型场景: Slack 通知、审批系统集成、操作审计日志上报。
mcp_tool — 直接调用 MCP 工具
直接调用已配置的 MCP 工具,无需绕道 shell 命令。
{
"type"
:
"mcp_tool"
,
"server"
:
"my-mcp-server"
,
"tool"
:
"validate_schema"
,
"input"
: {
"schema_path"
:
"$CLAUDE_FILE_PATH"
}
}
prompt — 使用 Claude 自身评估
把一段 prompt 发给 Claude(使用轻量模型),做快速语义判断。
{
"type"
:
"prompt"
,
"prompt"
:
"检查用户的请求是否包含敏感信息(API key、密码、token),如果包含,返回警告。"
}
适合场景: 判断代码是否\"看起来像\"在硬编码密钥,或请求是否\"语义上\"涉及删除生产数据。代价是比 类型慢一些。command
agent — 生成子代理多步验证
当验证逻辑需要读文件、执行命令、做多步推理时,使用此类型。
{
"type"
:
"agent"
,
"prompt"
:
"检查刚才修改的文件是否有对应的测试文件。如果有,运行测试确认通过。如果没有,提醒需要补充测试。"
}
这是最强大也最重量级的类型,只在真正需要复杂验证时才用。
4. 匹配与条件控制:精准触发规则
不是每个事件都需要触发 hook。匹配器和条件控制让你精确地定义\"只在特定情况下触发\"。
matcher — 工具名正则匹配
对工具名做正则匹配。只有匹配成功的事件才会进入 hook。matcher
{
"matcher"
:
"Bash"
}
{
"matcher"
:
"Write|Edit"
}
{
"matcher"
:
".*"
}
主要用于 matcher 和 PreToolUse 事件。PostToolUse
if — 精细条件过滤
在 if 之后做进一步的条件过滤,支持对工具参数的内容做匹配。matcher
{
"matcher"
:
"Bash"
,
"if"
:
"Bash(git push*)"
}
{
"matcher"
:
"Write"
,
"if"
:
"Write(*.env)"
}
的语法是 if,通配符支持 工具名(通配符模式)。*
once: true — 仅执行一次
{
"once"
:
true
}
设置为 后,这个 hook 在整个会话中只触发一次。适合做会话级的初始化操作。true
5. 退出码协议:控制执行流程
类型的 hook 通过退出码告诉 Claude Code 后续该怎么做。command
- 退出码 0 — 成功:hook 执行成功。Claude Code 会尝试解析 stdout 的内容为 JSON,根据
(注入上下文)、additionalContext(放行)、allow(阻止)等字段决定行为。deny
{
"allow"
:
true
,
"additionalContext"
:
"已验证,该操作安全。"
}
{
"deny"
:
"禁止直接 push 到 main 分支,请使用 PR 流程。"
}
- 退出码 2 — 阻塞错误:严重错误,Claude Code 会停下来,工具调用被取消。
- 其他退出码 — 非阻塞警告:记录 stderr 内容,但不会阻止操作继续执行。

6. 实战:构建 5 个常用 Hook
理论讲完了,下面是五个经过验证的 hook 配置,可直接放入 的 .claude/settings.json 字段中使用。hooks
6.1 自动格式化管道
每次 Claude 写完或编辑完文件,自动根据文件类型跑对应的格式化工具。
{
"hooks"
: {
"PostToolUse"
: [
{
"matcher"
:
"Write|Edit"
,
"type"
:
"command"
,
"command"
:
"bash -c 'FILE=\"$CLAUDE_FILE_PATH\"; if [ -z \"$FILE\" ]; then exit 0; fi; EXT=\"${FILE##*.}\"; case \"$EXT\" in js|ts|jsx|tsx|json|css|html|md) npx prettier --write \"$FILE\" 2>/dev/null ;; py) python3 -m black --quiet \"$FILE\" 2>/dev/null ;; go) gofmt -w \"$FILE\" 2>/dev/null ;; rs) rustfmt \"$FILE\" 2>/dev/null ;; *) exit 0 ;; esac; echo \"{\\\"additionalContext\\\": \\\"文件已自动格式化: $FILE\\\"}\"'"
,
"timeout"
:
30000
}
]
}
}
核心 Bash 命令展开:
#!/bin/sh
# 获取文件路径(环境变量传入)
FILE=
"$CLAUDE_FILE_PATH"
# 如果文件路径为空,直接退出
if
[ -z
"$FILE"
];
then
exit
0
fi
# 提取文件后缀名(不含 .)
EXT=
"${FILE##*.}"
# 根据不同后缀执行对应格式化工具
case
"$EXT"
in
# JS/TS/前端相关
js|ts|jsx|tsx|json|css|html|md)
npx prettier --write
"$FILE"
2>/dev/null
;;
# Python
py)
python3 -m black --quiet
"$FILE"
2>/dev/null
;;
# Go
go)
gofmt -w
"$FILE"
2>/dev/null
;;
# Rust
rs)
rustfmt
"$FILE"
2>/dev/null
;;
# 其他类型不处理
*)
exit
0
;;
esac
# 输出格式化成功的 JSON 结果
echo
"{\"additionalContext\": \"文件已自动格式化: $FILE\"}"
这个 hook 从 获取文件路径,提取扩展名,路由到对应格式化工具:JavaScript/TypeScript 用 Prettier,Python 用 Black,Go 用 gofmt,Rust 用 rustfmt,C++ 用 clang-format。通过 $CLAUDE_FILE_PATH 告知 Claude 文件已被格式化。additionalContext
纯 Python 项目简化版:
{
"hooks"
: {
"PostToolUse"
: [
{
"matcher"
:
"Write|Edit"
,
"if"
:
"Write(*.py)|Edit(*.py)"
,
"type"
:
"command"
,
"command"
:
"python3 -m black --quiet \"$CLAUDE_FILE_PATH\""
,
"timeout"
:
15000
}
]
}
}
6.2 安全防护栏
在所有 Bash 命令执行之前做安全检查,阻止危险操作并检测硬编码密钥。
{
"hooks"
: {
"PreToolUse"
: [
{
"matcher"
:
"Bash"
,
"type"
:
"command"
,
"command"
:
"bash -c 'INPUT=$(cat); CMD=$(echo \"$INPUT\" | python3 -c \"import sys,json; print(json.load(sys.stdin).get(\\\"command\\\",\\\"\\\"))\" 2>/dev/null); BLOCKED_PATTERNS=(\"rm -rf /\" \"rm -rf /*\" \"mkfs.\" \"dd if=\" \"> /dev/sda\" \"chmod -R 777 /\" \":(){ :|:& };:\"); for pat in \"${BLOCKED_PATTERNS[@]}\"; do if echo \"$CMD\" | grep -qF \"$pat\"; then echo \"{\\\"deny\\\": \\\"危险操作被阻止: 检测到 $pat\\\"}\" ; exit 0; fi; done; if echo \"$CMD\" | grep -qP \"git\\s+push\\s+.*--force\"; then echo \"{\\\"deny\\\": \\\"禁止 force push,请使用 --force-with-lease 替代\\\"}\" ; exit 0; fi; if echo \"$CMD\" | grep -qiP \"DROP\\s+(TABLE|DATABASE)\"; then echo \"{\\\"deny\\\": \\\"禁止直接执行 DROP 语句,请通过 migration 操作\\\"}\" ; exit 0; fi; echo \"{\\\"allow\\\": true}\"'"
,
"timeout"
:
5000
},
{
"matcher"
:
"Write|Edit"
,
"type"
:
"command"
,
"command"
:
"bash -c 'INPUT=$(cat); CONTENT=$(echo \"$INPUT\" | python3 -c \"import sys,json; d=json.load(sys.stdin); print(d.get(\\\"content\\\",d.get(\\\"new_string\\\",\\\"\\\")))\" 2>/dev/null); if echo \"$CONTENT\" | grep -qP \"(AKIA[0-9A-Z]{16}|sk-[a-zA-Z0-9]{48}|ghp_[a-zA-Z0-9]{36}|-----BEGIN (RSA |EC )?PRIVATE KEY-----)\"; then echo \"{\\\"deny\\\": \\\"检测到疑似硬编码密钥,请使用环境变量替代\\\"}\" ; exit 0; fi; echo \"{\\\"allow\\\": true}\"'"
,
"timeout"
:
5000
}
]
}
}
第一个 hook 拦截危险 shell 命令:、rm -rf /(毁灭性删除)、rm -rf /*(格式化磁盘)、mkfs.(强制推送)等。git push --force
第二个 hook 检测写入内容中的硬编码密钥:AWS Access Key(以 开头)、OpenAI/Anthropic API Key(以 AKIA 开头)、GitHub Personal Token(以 sk- 开头)、PEM 私钥。检测到时直接 deny。ghp_
6.3 Git 推送审批
在 push 之前确认目标分支和远程仓库,防止误推到 main/master。
{
"hooks"
: {
"PreToolUse"
: [
{
"matcher"
:
"Bash"
,
"if"
:
"Bash(git push*)"
,
"type"
:
"command"
,
"command"
:
"bash -c 'INPUT=$(cat); CMD=$(echo \"$INPUT\" | python3 -c \"import sys,json; print(json.load(sys.stdin).get(\\\"command\\\",\\\"\\\"))\" 2>/dev/null); if echo \"$CMD\" | grep -qP \"git\\s+push\\s+.*\\b(main|master)\\b\"; then BRANCH=$(echo \"$CMD\" | grep -oP \"(?<=push\\s)(\\S+\\s+)?\\K(main|master)\"); REMOTE=$(echo \"$CMD\" | grep -oP \"(?<=push\\s)\\S+\" | head -1); echo \"{\\\"deny\\\": \\\"禁止直接 push 到 $BRANCH 分支 (remote: $REMOTE)。请创建 PR 进行合并。\\\"}\" ; exit 0; fi; if echo \"$CMD\" | grep -qP \"--force(?!-with-lease)\"; then echo \"{\\\"deny\\\": \\\"禁止 --force push。如需强制推送,请使用 --force-with-lease 以保护远程上的他人提交。\\\"}\" ; exit 0; fi; echo \"{\\\"allow\\\": true}\"'"
,
"timeout"
:
5000
}
]
}
}
做两层检查:分支保护(检测到 push 到 main 或 master 时阻止)和远程仓库确认。
[AFFILIATE_SLOT_1]6.4 完成度验证
在 Claude 回复结束时,使用 prompt 类型 hook 做语义验证。
{
"hooks"
: {
"Stop"
: [
{
"type"
:
"prompt"
,
"prompt"
:
"请检查 Claude 刚才的操作是否满足以下条件:\n1. 如果修改了代码,是否有对应的测试覆盖?\n2. 如果修改了 API,是否更新了相关文档或注释?\n3. 是否有遗留的 TODO 或 FIXME 没有处理?\n4. 代码是否能正常编译/运行(基于上下文判断)?\n\n如果所有条件都满足或不适用,返回 PASS。\n如果有明确的遗漏,返回具体的遗漏项。"
}
]
}
}
这个 hook 让 Claude 用轻量模型快速判断任务是否真正完成,避免遗漏关键步骤。
6.5 Slack 通知集成
每次工具执行后,将操作记录发送到 Slack。
{
"hooks"
: {
"Stop"
: [
{
"type"
:
"agent"
,
"prompt"
:
"检查所有被修改的文件,运行项目的测试套件(如果有的话),确认测试通过。如果测试失败,输出失败的测试用例和错误信息。"
}
]
}
}
适合团队协作场景,让所有成员实时了解 Claude 的操作。
7. Skill 内的钩子作用域
Hook 也可以在 Skill 内部定义,作用域限定在该 Skill 激活时。这为特定任务提供了上下文相关的自动化逻辑。
{
"hooks"
: {
"PostToolUse"
: [
{
"matcher"
:
"Write|Edit"
,
"type"
:
"http"
,
"url"
:
"https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX"
}
]
}
}
当 Skill 被激活时,其内部的 hook 自动生效;当 Skill 被停用时,hook 也随之失效。这种设计让 hook 的组织更加模块化。
8. 调试与排查
Verbose 模式查看执行日志
使用 启动 Claude Code,可以查看每个 hook 的执行详情,包括触发事件、匹配结果、退出码和输出内容。--force
Hook 执行顺序的确定性保证
同一事件上的多个 hook 按照定义顺序执行。每个 hook 的输出可以影响后续 hook 的输入,但不会影响其他事件的 hook。
常见问题与解决方案
- Hook 未触发:检查 matcher 和 if 条件是否匹配
- 退出码 2 误报:检查 stderr 输出,避免将警告当作错误
- 性能问题:避免在高频事件上使用 agent 或 prompt 类型
小结
Hooks 系统是 Claude Code 中最强大的确定性控制机制。通过理解事件模型、选择合适的钩子类型、精准配置匹配条件,你可以构建从自动格式化到安全防护的完整自动化管道。记住核心原则:Skills 教 Claude 做什么,Hooks 确保它做对。善用这套工具,你的 AI 编程体验将变得更加可靠、高效和安全。
浙公网安备 33010602011771号