Hooks
事件驱动自动化——在关键时刻自动执行约束
不再靠人自觉 — 让质量门禁变成自动化的靠人自觉不靠谱
你有没有发现,"记得做"的事情经常被忘掉?
提交前忘跑测试
团队规定"提交前必须跑测试"。但你急着下班一敲 git commit 就走了。第二天 CI 红了,全组等你修。
不小心提交密钥
"别提交密钥"——说了无数遍。但某次赶进度,.env 文件混进了提交。密钥泄露,连夜轮换。
代码忘了格式化
审查 PR 时发现满屏的格式问题。不是什么大 bug,但每次都要花时间指出同样的格式问题,审查者和提交者都很烦。
开始之前
适合谁
- 需要把"规范检查、日志记录、自动修复"融入日常工作流的个人和团队
- 厌倦了口头约定"记得做 XXX"却总是被忘掉的人
- 了解命令和 Skill 的基本使用,想让流程更自动化的开发者
读完后你应该已经能
- 说清 Hooks 的触发时机和能做什么
- 设计典型用例的 Hook(安全门禁、格式校验、质量检查)
- 理解失败策略(提醒 / 阻断 / 自动修复)的选择依据
什么是 Hook
定义 → 白话 → 能做什么
Hook是在 Claude Code 事件发生时自动执行的 shell 命令。它们在特定动作的前后触发,不需要手动操作。
说白了,Hook 就是一个"自动触发的检查点"——你设好"什么时候检查"和"检查什么",之后每次到那个时间点,它就自动跑一遍。过了就放行,没过就拦截或提醒。
校验
检查输入/输出是否符合预期
格式化
写入前自动格式化代码
告警
发现问题时提醒用户
阻断
阻止不合规的操作执行
审计
记录所有操作到日志
自动修复
检测到问题后自动修正
四类事件
Hook 能挂在哪些时间点上
| 类别 | 事件 | 典型用途 |
|---|---|---|
| Tool | PreToolUse、PostToolUse、PostToolUseFailure、PermissionRequest | 格式化、安全扫描、日志、自动审批 |
| Session | SessionStart、SessionEnd、Stop、StopFailure、SubagentStart、SubagentStop | 初始化、清理、审计、子 agent 追踪 |
| Task | UserPromptSubmit、TaskCompleted、TaskCreated、TeammateIdle | 输入校验、任务追踪、协作调度 |
| Lifecycle | ConfigChange、CwdChanged、FileChanged、PreCompact、PostCompact、WorktreeCreate、WorktreeRemove、Notification、InstructionsLoaded、Elicitation、ElicitationResult | 环境变更、上下文管理、工作树、通知、交互问答 |
PreToolUse(工具执行前拦截或修改)和 PostToolUse(工具执行后校验或记录)。先掌握这两个,其他的按需取用。
配置方式
在 settings.json 中声明 Hook
{
"hooks": {
"PreToolUse": [{
"matcher": "Write",
"hooks": ["~/.claude/hooks/format-code.sh"]
}],
"PostToolUse": [{
"matcher": "Write",
"hooks": [{
"type": "command",
"command": "prettier --write $CLAUDE_FILE_PATH"
}]
}]
}
}
两种 hook 格式
脚本路径(字符串):适合复杂逻辑,指向一个 .sh 文件。
内联命令(对象):适合一行命令搞定的事,如 {"type": "command", "command": "prettier --write $CLAUDE_FILE_PATH"}。不用单独写脚本文件。
Hook 可用的环境变量
$CLAUDE_FILE_PATH— 当前操作的文件路径$CLAUDE_TOOL_NAME— 当前使用的工具名$CLAUDE_SESSION_ID— 当前会话 ID$CLAUDE_PROJECT_DIR— 项目目录路径
三种结果
退出码决定 Hook 的行为——通过、提醒、阻断
| 退出码 | 效果 | 什么时候用 |
|---|---|---|
0 | 通过,操作正常继续 | 检查全部通过 |
1 | 提醒,不阻断但提示用户 | 非致命问题,建议关注 |
2 | 阻断,操作被取消 | 严重违规,必须拦截 |
安全门禁
写入文件前自动扫描敏感信息
# 文件:~/.claude/hooks/security-scan.sh
#!/bin/bash
FILE="$CLAUDE_FILE_PATH"
CONTENT=$(cat "$FILE" 2>/dev/null)
# 检查是否包含密钥模式
if echo "$CONTENT" | grep -qE '(AKIA|sk-|api_key|secret|password)\s*[:=]'; then
echo "🚨 检测到可能的密钥或敏感信息!"
echo "请检查文件: $FILE"
exit 2 # 阻断操作
fi
echo "✅ 安全检查通过"
exit 0
格式校验
确保生成的 API 文档包含必需字段
# 文件:~/.claude/hooks/check-api-docs.sh
#!/bin/bash
FILE="$CLAUDE_FILE_PATH"
if [[ "$FILE" == *api-docs* ]]; then
for section in "## 概述" "## 参数" "## 返回值" "## 示例"; do
if ! grep -q "$section" "$FILE"; then
echo "⚠️ API 文档缺少必需章节: $section"
exit 1 # 提醒但不阻断
fi
done
fi
exit 0
质量检查
提交前自动运行测试
# 文件:~/.claude/hooks/pre-commit-test.sh
#!/bin/bash
# PreToolUse hook for Bash(git commit*)
if ! npm test 2>/dev/null; then
echo "❌ 测试失败!请修复后再提交。"
exit 2 # 阻断提交
fi
echo "✅ 所有测试通过"
exit 0
更多示例
三个常见场景的 Hook 脚本
记录 Bash 命令
# log-bash.sh — PostToolUse
#!/bin/bash
echo "[$(date '+%Y-%m-%d %H:%M')] " \
"cmd=$CLAUDE_TOOL_NAME " \
"file=$CLAUDE_FILE_PATH" \
>> ~/.claude/hooks/audit.log
exit 0
每次工具调用后写审计日志,exit 0 不阻断。
校验用户输入
# validate-prompt.sh — UserPromptSubmit
#!/bin/bash
PROMPT=$(cat)
if echo "$PROMPT" | grep -qiE '(删除所有|drop table)'; then
echo "⚠️ 输入包含高危关键词"
exit 1
fi
exit 0
检测用户输入中的危险指令,提醒但不阻断。
发送团队通知
# notify-team.sh — Notification
#!/bin/bash
MSG=$(cat)
curl -s -X POST "$WEBHOOK_URL" \
-H 'Content-type: application/json' \
-d "{\"text\":\"$MSG\"}" \
>/dev/null 2>&1
exit 0
通过 Webhook 把通知转发到团队聊天工具。
安装与最佳实践
把 Hook 脚本放到正确位置,并给执行权限
# 1. 创建 hooks 目录
$ mkdir -p ~/.claude/hooks
# 2. 复制 Hook 脚本
$ cp security-scan.sh ~/.claude/hooks/
$ cp format-code.sh ~/.claude/hooks/
# 3. 赋予执行权限
$ chmod +x ~/.claude/hooks/*.sh
# 4. 在 settings.json 中配置触发规则
| 配置位置 | 影响范围 | 适合放什么 |
|---|---|---|
~/.claude/settings.json | 所有项目 | 个人通用的安全扫描、格式化 |
.claude/settings.json | 当前项目 | 团队共享的质量门禁、项目规范 |
性能要求
Hook 执行时间应控制在 1 秒以内。超时的 Hook 会拖慢整个工作流。耗时操作(完整测试套件)放 CI,Hook 只做快速检查。
权限提示自动化
PermissionRequest 事件可以自动审批特定工具调用。比如自动允许 Read 操作,但 Bash(rm *) 仍然需要手动确认——减少弹窗但不降低安全性。
单一职责
每个 Hook 只做一件事。不要在一个脚本里同时格式化、扫描密钥、跑测试。分开写便于调试、单独开关、逐步升级失败策略。
先本地再上线
新 Hook 先用 exit 1(提醒模式)在本地观察几天。确认没有误报后再改成 exit 2(阻断)。不要一上来就阻断——会把正常工作流卡住。
排错指南
误报太多
症状:规则太严,正常操作也被阻断,同事开始绕过 Hook。
修复:先降级为"提醒"模式(exit 1)。收集一周的告警数据,调整规则后再升级为"阻断"。
Hook 太慢
症状:每次操作都要等好几秒,拖慢整个工作流。
修复:保持 Hook 短小精悍。避免在 Hook 中跑完整测试套件——只做最关键的快速检查。耗时操作放到 CI。
别人跑不了
症状:你的 Hook 在别人机器上跑不通。
修复:用标准工具,不依赖特定环境。如果必须依赖,在 Hook 开头检查依赖是否可用,不可用则 exit 0(静默跳过)。
CI 环境差异
症状:本地 Hook 正常,CI 环境行为不同。
修复:注意环境变量和路径差异。CI 环境可能没有交互式权限确认——用 --permission-mode 控制。
配置语法错误
症状:Hook 配了但不生效,Claude Code 启动时报错或静默忽略。
修复:用 cat ~/.claude/settings.json | python3 -m json.tool 检查 JSON 语法。常见问题:缺少逗号、括号不匹配、数组/对象嵌套错误。
版本不兼容
症状:某些事件在旧版 Claude Code 中不被识别。
修复:运行 claude --version 检查版本。新事件(如 WorktreeCreate、Elicitation)需要较新版本。参考 更新日志 确认支持情况。
动手练习
设计一个"防止提交临时代码"的 Hook
console.log、debugger、TODO: 等临时调试代码。发现时提醒,但不阻断。
- 确定触发点(哪个事件)
- 编写检查逻辑(扫描什么内容)
- 决定失败策略(提醒 or 阻断)
- 思考:为什么这个场景用"提醒"而不是"阻断"?
参考答案
# 文件:~/.claude/hooks/check-debug-code.sh
#!/bin/bash
# PreToolUse hook for Bash(git commit*)
STAGED=$(git diff --cached --name-only --diff-filter=ACM \
| grep -E '\.(js|ts|jsx|tsx)$')
if [ -z "$STAGED" ]; then
exit 0
fi
FOUND=0
for file in $STAGED; do
if git diff --cached "$file" | grep -qE \
'^\+.*(console\.log|debugger|TODO:)'; then
echo "⚠️ $file 中包含调试代码"
FOUND=1
fi
done
if [ $FOUND -eq 1 ]; then
echo ""
echo "发现临时调试代码,建议清理后再提交。"
echo "如确认需要提交,可忽略此提醒。"
exit 1 # 提醒但不阻断
fi
exit 0
console.log 是故意留的调试日志,有些 TODO: 是标记后续优化点。硬性阻断会误伤正常开发。提醒给开发者选择权。
延伸阅读
继续本课程
- 下一章:Plugins — 把 Hooks 打包成可安装的团队标准
- Checkpoints — 与 Hooks 配合做安全试错
- Slash Commands — 用命令快速触发常用操作
- Skills — 把复杂工作流封装成可复用的技能
- Advanced Features — 进阶功能与高级配置
官方文档
- Official Hooks Documentation — 官方 Hook 配置与事件参考
- CLI Reference — 命令行参数与环境变量大全
本章源文件 (点击查看完整内容)
README.md— Hooks 完整配置指南