Hooks 指南
hooks 是 Claude Code 的“事件触发自动化”机制。它允许你在某些时机自动做点事情,例如:
- 调工具前先检查风险
- 写完文件后自动格式化
- 提交前跑测试
- 结束前记录日志或上下文
如果你已经会 slash commands 和 CLAUDE.md,下一步最值得掌握的自动化能力通常就是 hooks。
hooks 是什么
你可以把 hook 理解成一条规则:
- 某个事件发生
- 匹配某个工具或场景
- 自动执行一个动作
这些动作可以是:
- shell command
- HTTP webhook
- prompt 型判断
- agent 型评估
hooks 最大的价值,是把“你本来每次都要手动做的检查”变成自动流程。
常见配置位置
~/.claude/settings.json:用户级,对所有项目生效.claude/settings.json:项目级,适合团队共享.claude/settings.local.json:本地项目配置,不建议提交- plugin 内的
hooks/hooks.json - 某些 skill / subagent frontmatter 内的 component-scoped hooks
如果你是新手,建议先从用户级或项目级配置开始。
从 v2.1.218+ 起,project agent frontmatter 里的 hooks 只有在 agent 文件所在目录通过 workspace trust 后才运行。这个限制不会因为 hook 写在 frontmatter 里就自动绕过。
基本结构
{
"hooks": {
"EventName": [
{
"matcher": "ToolPattern",
"hooks": [
{
"type": "command",
"command": "your-command-here",
"timeout": 60
}
]
}
]
}
}
关键字段说明
| 字段 | 作用 | 示例 |
|---|---|---|
hooks |
hook 顶层配置入口 | { "PreToolUse": [...] } |
matcher |
匹配工具名或模式 | "Write"、"Edit|Write"、"Write,Edit"、"*" |
type |
hook 类型 | "command"、"http"、"mcp_tool"、"prompt"、"agent" |
command |
执行的 shell 命令 | "$CLAUDE_PROJECT_DIR/.claude/hooks/format.sh" |
timeout |
超时秒数 | 30 |
once |
每会话只跑一次 | true |
注意:这些字段属于配置协议的一部分,不要为了中文化把它们翻掉。
用 if 按工具参数继续收窄匹配
matcher 负责匹配工具名,例如 "Write"、"Edit|Write" 或 "*"。
从 v2.1.172+ 起,如果你还想按工具参数继续过滤,比如只在修改 src/ 目录时跑 lint,或只在读取 .env 时做拦截,可以在具体 hook handler 上加 if。
if 使用 permission-rule 语法,也就是 ToolName(pattern) 这种形式。常见例子:
Edit(src/**):只匹配src/下的编辑Read(.env):匹配当前目录及子目录里的.envRead(~/.ssh/**):匹配用户 SSH 目录读取Bash(git push *):只匹配git push相关命令
v2.1.214+范围变化:在 hookif条件里,单段dir/**(例如Edit(src/**))现在只匹配<cwd>/dir,不会再匹配任意深度的foo/dir/**。如果确实要匹配任意深度,请写**/dir/**。这个收窄只影响 hookif和 allow-rule 自动批准;deny / ask permission rules 里的dir/**仍按任意深度理解。
注意位置:if 是 hooks 数组里某个 handler 的字段,和 type、command 同级,不是写在 matcher 上。
{
"hooks": {
"PreToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"if": "Edit(src/**)",
"command": "./hooks/lint-src.sh"
}
]
},
{
"matcher": "Read",
"hooks": [
{
"type": "command",
"if": "Read(.env)",
"command": "./hooks/block-secret-read.sh"
}
]
}
]
}
}
这里的 if、matcher、type、command 都是配置 key,不能为了中文化改名。
matcher 怎么用
| 形式 | 含义 | 例子 |
|---|---|---|
| 精确匹配 | 只匹配某个工具 | "Write" |
| 正则匹配 | 匹配多个工具 | "Edit|Write" |
| 逗号列表 | 匹配任一列出的工具;v2.1.191+ 起生效 |
"Write,Edit" |
| 全匹配 | 匹配全部工具 | "*" 或 "" |
| MCP 工具模式 | 匹配 MCP 工具 | "mcp__memory__.*" |
从 v2.1.195+ 起,matcher 会按更精确的规则匹配工具名。带连字符的 MCP tool name 不会再因为子串重叠而误触发另一个工具;"Write,Edit" 这类逗号列表会按“任一工具命中”处理,而不是像旧版本那样静默不触发。
如果你配的是 InstructionsLoaded,它还有几种常见 matcher 值:
| matcher 值 | 含义 |
|---|---|
session_start |
会话刚启动时加载的 instructions |
nested_traversal |
向下遍历目录时加载的 instructions |
path_glob_match |
通过 path glob 匹配加载的 instructions |
如果你不确定先配什么,最常见的起点是:
BashWriteEdit|Write
五种 hook 类型
1. command(本地命令)
最常见的类型。适合:
- shell 校验
- 安全扫描
- 自动格式化
- 日志记录
{
"type": "command",
"command": "python3 \"$CLAUDE_PROJECT_DIR/.claude/hooks/validate.py\"",
"timeout": 60
}
args 形式:直接 exec,不走 shell
从 v2.1.139 开始,command hook 还支持 args 形式,直接用 execve() 拉起目标二进制,不经过 shell 解析。
2. http(HTTP 调用)
适合把事件发给 webhook 或外部系统。
{
"type": "http",
"url": "https://example.com/hook"
}
continueOnBlock:让 Claude 看到 hook 的阻断反馈后再调整
从 v2.1.139 开始,PostToolUse hook 可以配 continueOnBlock: true。默认情况下 hook 返回 "decision": "block" 会直接终止当前 turn;加上这个字段后,Claude 会把阻断原因当成一个 tool_result 读回来,再决定如何重试或换方案。
常见用途:
- 通知系统
- 团队消息流
- 外部审计系统
3. mcp_tool(MCP 工具调用)
这是上游在 v2.1.118 明确补进来的 hook 类型,适合:
- 你已经把验证逻辑做成 MCP server
- 不想每次再绕一层 shell
- 希望 hook 直接调用 MCP tool
典型配置会直接指定:
servertool
而不是本地 command 或远程 url。
4. prompt(提示词)
让模型根据 prompt 判断是否该继续,常见于:
- 任务完成检查
- 结束前质量判断
- prompt 合规性判断
5. agent(代理)
让 Claude 用独立 agent 做更复杂的评估,适合:
- 架构规则检查
- 多步验证
- 比较复杂的质量门禁
常见事件
当前最值得先掌握的事件:
| 事件 | 什么时候触发 | 最常见用途 |
|---|---|---|
PreToolUse |
工具执行前 | 校验、阻止、改输入 |
PostToolUse |
工具执行后 | 验证、补上下文、记录 |
Setup |
每个 session 的一次性初始化阶段 | 安装依赖、准备工具、生成临时环境 |
UserPromptSubmit |
用户提交 prompt 时 | prompt 校验 |
Stop / SubagentStop |
Claude / subagent 结束时 | 完成度判断 |
SessionStart / SessionEnd |
会话开始 / 结束 | 初始化、清理、日志 |
更完整的生态还包括:
PermissionRequestPermissionDeniedPostToolUseFailureUserPromptExpansionPostToolBatchNotificationMessageDisplayDirectoryAddedTaskCreatedTaskCompletedCwdChangedWorktreeCreateWorktreeRemove
Notification 的 matcher 还新增了两个和后台 agent 有关的值(v2.1.198+):
agent_needs_input:后台 agent 需要用户补充输入agent_completed:后台 agent 已完成
如果你是新手,不需要一上来把所有事件都学完。
截至
v2.1.219+,上游共有 31 个 hook 事件、5 种 hook 类型。类型(command、http、mcp_tool、prompt、agent)决定 hook 如何运行,事件决定它何时运行,两者不是同一个分类轴。如果你还在参考旧资料里的 25、28、29 或 30 个事件,优先以当前版本为准。MessageDisplay会在 assistant message text 显示过程中触发;新增的DirectoryAdded会在/add-dir或 SDKregister_repo_root注册新工作目录后触发。
最实用的三个起步场景
场景 1:提交前跑测试
这是最容易感受到 hooks 价值的起点。
mkdir -p ~/.claude/hooks
cp 06-hooks/pre-commit.sh ~/.claude/hooks/
chmod +x ~/.claude/hooks/pre-commit.sh
常见配置思路:
- 监听
PreToolUse - matcher 设为
Bash - 在脚本里判断是否是
git commit
场景 2:写完文件自动格式化
适合在 PostToolUse + Write|Edit 场景下做代码格式化或轻量校验。
场景 3:安全扫描
适合在文件修改后,自动扫明显危险模式,例如 secrets、危险命令或敏感字符串。
hook 的输入输出是怎么工作的
输入
hooks 通常通过 stdin 接收 JSON 输入。
所有 hooks 都会收到 session、工作目录和事件等基础字段。v2.1.196+ 起还会收到 prompt_id,它是当前 prompt 的 UUID,可与 OpenTelemetry 的 prompt.id 属性关联,用来串联一次用户输入、hook 日志和 telemetry。
输出
可以通过:
- exit code
- stdout JSON
把结果反馈回 Claude。
常见控制方式包括:
allowdenyaskdeferupdatedInputadditionalContexthookSpecificOutput.updatedToolOutput
shell hook 的退出码不是普通脚本里的“任意非零都算阻断”:0 表示成功;只有 exit 2 才会按事件语义阻断,并且阻断原因必须写到 stderr。其他非零退出码只是非阻断错误,例如 PreToolUse 中用 exit 1 不会阻止工具调用。PostToolUse 已发生在工具执行之后,exit 2 只能把 stderr 反馈给 Claude,不能撤销已经完成的操作。
PreToolUse 的 permissionDecision 可返回 allow、deny、ask 或 defer。defer 用于把工具调用留待稍后恢复,并会忽略 permissionDecisionReason、updatedInput 和 additionalContext。多个 hook 意见冲突时,优先级是 deny > defer > ask > allow;permission rules 中的 deny / ask 仍会继续参与判断。
hooks 现在也能感知当前 effort level
从 v2.1.133+ 开始,hook 输入和 Bash 子进程环境里都能拿到当前 effort 信息。
这对你想做“高 effort 时多跑一层校验,低 effort 时只做轻量检查”的场景很有用。
你可以关注两类入口:
- hook 输入 JSON 里的
effort.level - Bash 子进程环境变量里的
CLAUDE_EFFORT
另外还有一个对排错很实用的环境变量:
CLAUDE_CODE_SESSION_ID:可用来把 Bash 日志和 hook telemetry 对到同一个 session
不要依赖未列入官方 reference 的 Stop / SubagentStop 字段
上一轮同步曾按 release notes 记录过 background_tasks 和 session_crons,但它们没有出现在当前官方 hooks reference 的字段列表里。为了避免中文用户写出依赖不稳定输入的 hook,本版移除了这两个字段的用法说明。
如果你想在 session 结束前做兜底检查,更稳的做法是让 hook 读取你自己维护的状态文件、测试锁文件或 CI 状态,而不是假设 Stop / SubagentStop 输入一定包含这两个数组。
updatedToolOutput 现在不只限 MCP 工具
上游在 v2.1.121+ 扩大了 hookSpecificOutput.updatedToolOutput 的适用范围:
它现在对 Bash、Edit、Read 等普通工具也生效,不再只是 MCP tool。
这意味着 PostToolUse hook 可以在 Claude 看到工具输出前先做一层处理,例如:
- 去掉命令输出里的 ANSI 颜色码
- 过滤噪音日志
- 脱敏 secrets
- 把复杂 diff 改成更容易读的摘要
返回结构保持 key 原样,不要翻译:
{
"hookSpecificOutput": {
"hookEventName": "PostToolUse",
"updatedToolOutput": "<plain-text output>"
}
}
SessionStart 可以重扫 skills 并设置 session 标题
从 v2.1.152+ 开始,SessionStart hook 可以通过 stdout JSON 做两件很实用的事:
{
"reloadSkills": true,
"hookSpecificOutput": {
"sessionTitle": "Payments migration"
}
}
reloadSkills: true:在当前 session 里重新扫描 skills,效果类似/reload-skillshookSpecificOutput.sessionTitle:设置 session 启动或恢复时显示的标题
这些 key 是协议字段,不要翻译。
SessionStart 的 matcher / source 还包括 startup、resume、clear、compact 和 fork。从 v2.1.214+ 起,fork 出来的 session 会报告 "fork",不再伪装成 "resume"。
另外,status-line command scripts 现在会收到 COLUMNS 和 LINES 环境变量。你可以根据终端宽度输出短状态栏或详细状态栏,例如窄窗口只显示分支和测试状态,宽窗口再补充 token / session 信息。
这轮上游同步后要特别注意什么
上游最近把一批示例 shell hooks 统一到了 stdin JSON 协议,并补了 Windows Git Bash 兼容性。
这意味着你现在更应该按这种方式理解示例脚本:
- 从
stdin读 JSON 输入 - 用
file_path、command、user_prompt这类字段取值 - 需要阻止或修改行为时,返回 Claude Code 认可的 stdout JSON;简单阻断则向 stderr 输出理由并
exit 2
如果你还在按“第一个位置参数就是文件路径”来写,很容易和新版本示例脱节。
另外,v2.1.119 之后 PowerShell 侧的 auto-approve 行为也更接近 Bash 了。
如果你在 Windows 上跑 Claude Code,这意味着权限模式的体验比以前更一致。
还有一个安全修复值得单独记住:v2.1.145 关闭了 Bash 裸环境变量 allowlist 的自动批准漏洞。以前如果只允许了 FOO=bar,某些 FOO=bar somecommand 形式的命令可能被误认为已允许;现在这类命令会重新触发权限提示。需要自动批准时,请写覆盖完整命令的 Bash(...) 规则,而不是只允许环境变量赋值本身。
从 v2.1.163+ 起,Stop / SubagentStop hook 还可以返回 hookSpecificOutput.additionalContext,把上下文补给 Claude 并继续当前 turn,而不是用 "decision": "block" 这种容易显示成错误标签的方式“硬拦住”。
{
"hookSpecificOutput": {
"hookEventName": "Stop",
"additionalContext": "请先运行测试,再说明任务完成。"
}
}
这里的 hookSpecificOutput、hookEventName、additionalContext、Stop 都是协议字段,不要翻译。
本目录示例脚本怎么用
| 文件 | 用途 | 适合什么时候先试 |
|---|---|---|
pre-commit.sh |
提交前跑测试 | 第一个推荐示例 |
format-code.sh |
自动格式化 | 写代码后自动收尾 |
security-scan.sh |
安全扫描 | 团队规范较严格时 |
pre-tool-check.sh |
Bash 高风险命令预检查 | 想先拦截危险命令时 |
validate-prompt.sh |
prompt 校验 | 控制输入质量 |
log-bash.sh |
记录命令使用 | 做审计或追踪 |
dependency-check.sh |
依赖清单变更后做漏洞扫描 | 团队依赖治理较严格时 |
notify-team.sh |
通知团队 | 配合外部消息系统 |
context-tracker.py |
上下文追踪 | 调试长会话问题 |
session-end.sh |
会话结束时记录学习模块 | 想跟踪自己学到哪一章时 |
关于 auto-adapt-mode 的更新
如果你之前看过旧资料,可能见过一种思路:
- 在
PostToolUse里记录你批准过的命令 - 自动把这些批准“泛化”进本地权限配置
上游最近已经把这条路线下掉了,不再推荐继续使用旧的:
06-hooks/auto-adapt-mode.py
新的建议方式是:
- 使用一次性脚本
09-advanced-features/setup-auto-mode-permissions.py - 直接把一组更保守、更可控的权限规则写进
~/.claude/settings.json - 再通过命令行参数按需放开 edits、tests、git writes、package installs、GitHub write 等能力
这样做的好处是:
- 不再依赖“边用边学你的批准”
- 配置更可预测
- 更适合团队分享和审阅
- 对中国用户来说,也更容易解释“当前到底开了哪些权限”
如果你在意 Auto Mode 但又没有 Team plan,优先看:
SessionEnd 学习进度记录
上游这次新增了一个更贴近学习仓库定位的 hook:06-hooks/session-end.sh。
它会在 Claude Code 会话结束时提示你输入本次学习过的模块,例如:
06,07
然后把记录追加到:
~/.claude-howto-progress.json
这个路径放在仓库外面,所以你之后 git pull 或同步上游时,不会把自己的学习记录覆盖掉。
为什么用 SessionEnd,不是 Stop
Stop:Claude 每次回复结束后都会触发SessionEnd:整场会话结束时触发一次
如果目标是写“今天学了哪些模块”的学习日志,SessionEnd 更自然。否则每一轮对话后都弹一次输入,体验会很烦。
为什么脚本里要用 /dev/tty
hook 的 stdin 默认已经被 Claude Code 用来传 JSON payload。
如果脚本还想和用户交互,read 就不能直接读 stdin,而要显式从 /dev/tty 读:
read -r INPUT </dev/tty
这也是写交互式 hooks 时很容易踩的坑。
安装方式
mkdir -p .claude/hooks
cp 06-hooks/session-end.sh .claude/hooks/
chmod +x .claude/hooks/session-end.sh
然后在 .claude/settings.json 里加入:
{
"hooks": {
"SessionEnd": [
{
"hooks": [
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR/.claude/hooks/session-end.sh\""
}
]
}
]
}
}
输出示例:
{
"sessions": [
{
"date": "2026-04-28",
"time": "09:18",
"modules": ["06-hooks", "07-plugins"],
"notes": "试了 SessionEnd hook,并理解了 /dev/tty 的用途"
}
]
}
配套的本地进度页面
如果你更喜欢可视化勾选,可以直接打开:
open local-progress/index.html
这个页面覆盖 10 个模块,进度保存在浏览器 localStorage,不会写进仓库。
导出的 JSON 会被 .gitignore 忽略,适合自己备份,不适合提交。
hooks 配置里哪些绝对不能翻
hooksmatchertypecommandtimeoutreloadSkillssessionTitleMessageDisplayDirectoryAddedCOLUMNSLINES- 事件名,例如
PreToolUse - JSON key
- 实际命令行片段
可以翻译:
- 注释
- 使用说明
- README 正文
不能翻译:
- 协议字段
- 事件名
- 命令
中国用户特别注意
1. shell 差异
很多示例默认更偏 Unix / macOS / Linux 风格。 Windows 用户请先确认你当前用的是:
- PowerShell
- Git Bash
- WSL
2. 环境依赖
如果 hook 里调用:
pythonnodeuvnpmpytest
请先确认本机路径和环境变量,否则“配置看起来对,运行却没效果”非常常见。
如果你在 Windows 上用 Git Bash,这一点更重要。上游最近的脚本更新,核心目的之一就是避免脚本只在 macOS / Linux 上能跑。
3. 网络与代理
如果 hook 会发 HTTP 请求,记得考虑:
- 公司代理
- TLS 证书
- 外部服务可访问性
debugging 和排错思路
如果 hook 没生效,优先按这个顺序排查:
- 事件名是否正确
matcher是否匹配到了目标工具- 路径是否正确
- 脚本是否可执行
- 脚本内部依赖是否存在
- stdout / exit code 是否符合预期
如果 hook 触发了但效果不对,重点看:
- 命令是否真的执行成功
- 你的 hook 是否过重、太慢
- 是否在错误事件上绑定了错误的逻辑
最佳实践
- 从一个轻量 hook 开始,不要一口气加很多
- 优先做“高频、确定、低风险”的自动动作
- 不要让 hook 变成新的复杂系统
- 先让 hook 稳,再考虑把它放进 plugin
- 对中国用户来说,环境说明和 shell 差异提示非常重要
推荐下一步
- 想做自动触发的复用能力:看 03-skills
- 想接入外部系统:看 05-mcp
- 想把一整套流程打包分发:看 07-plugins