主题
Hooks 钩子
Hook 是在 Claude Code 生命周期的固定节点上自动执行的 shell 命令。
它和 CLAUDE.md 的本质区别:CLAUDE.md 里的规则是请求模型遵守,模型可能忘;Hook 是由程序强制执行的,模型绕不过去。
需要「每次改完代码自动格式化」「提交前必须跑测试」这类硬约束时,用 Hook,不要靠写在
CLAUDE.md里祈祷。
配置位置
写在 settings.json 的 hooks 字段里:
| 文件 | 作用范围 |
|---|---|
~/.claude/settings.json | 所有项目 |
<项目根>/.claude/settings.json | 该项目,提交到 Git |
<项目根>/.claude/settings.local.json | 该项目,个人本地,加 .gitignore |
可用的钩子点
| 事件 | 触发时机 | 典型用途 |
|---|---|---|
PreToolUse | 工具执行前 | 拦截危险命令、校验参数 |
PostToolUse | 工具执行后 | 自动格式化、跑 lint |
UserPromptSubmit | 你按下回车后 | 注入上下文、记录审计日志 |
Notification | 需要你注意时 | 发系统通知、响铃 |
Stop | 一轮回答结束 | 跑测试、汇总改动 |
SessionStart | 会话开始 | 打印环境信息 |
例 1:写完文件自动格式化
json
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "jq -r '.tool_input.file_path' | { read f; case \"$f\" in *.ts|*.tsx) npx prettier --write \"$f\" ;; *.go) gofmt -w \"$f\" ;; *.py) ruff format \"$f\" ;; esac; }"
}
]
}
]
}
}Hook 从 stdin 收到一个 JSON,里面有 tool_name、tool_input 等字段,用 jq 取出来即可。
例 2:拦截危险命令
json
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "jq -r '.tool_input.command' | grep -qE 'rm -rf /|DROP DATABASE|git push --force' && { echo '拒绝执行高危命令' >&2; exit 2; } || exit 0"
}
]
}
]
}
}退出码语义:
| 退出码 | 效果 |
|---|---|
0 | 放行 |
2 | 阻止这次工具调用,stderr 的内容回传给模型 |
| 其它 | 报错但不阻止 |
exit 2 是唯一能真正拦住模型的方式。
例 3:一轮结束自动跑测试
json
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "cd \"$CLAUDE_PROJECT_DIR\" && go test ./... 2>&1 | tail -20"
}
]
}
]
}
}别在 Stop 里跑很慢的东西
Stop 每一轮都会跑。全量测试要两分钟的话,你每问一句都要等两分钟。 慢任务改用 PostToolUse + matcher 精确匹配,或者干脆手动跑。
例 4:完成时响铃
json
{
"hooks": {
"Notification": [
{
"hooks": [
{ "type": "command", "command": "afplay /System/Library/Sounds/Glass.aiff" }
]
}
]
}
}macOS 用 afplay;Linux 用 paplay;Windows 用 PowerShell 的 [console]::beep(800,300)。
可用的环境变量
| 变量 | 含义 |
|---|---|
$CLAUDE_PROJECT_DIR | 当前项目根目录 |
stdin JSON .tool_name | 触发的工具名 |
stdin JSON .tool_input | 工具入参对象 |
stdin JSON .session_id | 会话 ID |
调试 Hook
bash
claude --debug会打印每个 hook 的命令、退出码和输出。Hook 不生效时最常见的三个原因:
- JSON 语法错误——整个
settings.json失效,用jq . ~/.claude/settings.json验证 - 命令里的引号没转义——JSON 字符串里的
"要写成\" - matcher 没匹配上——
matcher是正则,匹配的是工具名(Bash、Edit、Write、Read…)
安全提醒
Hook 会用你的权限执行任意命令
不要直接复制来路不明的 hook 配置,尤其是别人仓库里的 .claude/settings.json。
克隆陌生项目后,先看一眼 .claude/settings.json 里有没有 hooks,再启动 Claude Code。
和成本的关系
Hook 本身不产生 API 调用,不花钱。但它能间接省钱:
- 自动格式化 → 模型不用再花一轮去修格式
- 自动跑测试 → 错误当场暴露,不用来回问「跑一下测试」
- 拦截危险命令 → 不用事后花上下文去补救
