Claude Code 的 Hooks 系统允许你在特定事件发生时自动执行脚本。比如在写入文件后自动格式化、在执行命令前做安全检查、在会话结束时发送通知。本文通过 7 个完整的实战案例,带你掌握 Hooks 的高级用法。
一、Hooks 基础概念
1.1 四种 Hook 类型
| Hook 类型 | 触发时机 | 典型用途 |
|---|---|---|
PreToolUse | 工具调用之前 | 安全检查、权限拦截 |
PostToolUse | 工具调用之后 | 自动格式化、日志记录 |
Notification | 需要通知用户时 | 发送消息到 Slack/钉钉 |
Stop | Claude Code 完成回复时 | 自动测试、自动 lint |
1.2 配置位置
Hooks 在.claude/settings.json中配置:
{ "hooks": { "PreToolUse": [...], "PostToolUse": [...], "Notification": [...], "Stop": [...] } }1.3 Hook 配置结构
{ "matcher": "toolName 的匹配规则(正则或字符串)", "hooks": [ { "type": "command", "command": "要执行的 shell 命令" } ] }Hook 脚本通过 stdin 接收 JSON 格式的事件数据,可以据此做出判断。
二、实战案例
案例 1:文件写入后自动格式化(PostToolUse)
每次 Claude Code 写入或编辑文件后,自动运行 Prettier 格式化。
{ "hooks": { "PostToolUse": [ { "matcher": "Write|Edit", "hooks": [ { "type": "command", "command": "filepath=$(echo $CLAUDE_TOOL_INPUT | jq -r '.file_path // .filePath // empty') && if [ -n \"$filepath\" ] && echo \"$filepath\" | grep -qE '\\.(ts|tsx|js|jsx|vue|css|json)$'; then npx prettier --write \"$filepath\" 2>/dev/null; fi" } ] } ] } }这个 Hook 只对前端相关文件(.ts/.tsx/.js/.jsx/.vue/.css/.json)执行格式化,避免对其他文件产生副作用。
案例 2:危险命令拦截(PreToolUse)
阻止 Claude Code 执行危险的 shell 命令,如rm -rf、DROP TABLE等。
{ "hooks": { "PreToolUse": [ { "matcher": "Bash", "hooks": [ { "type": "command", "command": "cmd=$(echo $CLAUDE_TOOL_INPUT | jq -r '.command') && if echo \"$cmd\" | grep -qiE 'rm\\s+-rf\\s+/|drop\\s+table|drop\\s+database|truncate\\s+table|:(){ :|:& };:'; then echo 'BLOCKED: 检测到危险命令,已拦截' >&2; exit 2; fi" } ] } ] } }当 Hook 脚本以退出码 2 退出时,Claude Code 会阻止该工具调用。退出码含义:
| 退出码 | 含义 |
|---|---|
| 0 | 允许执行,不修改 |
| 1 | Hook 执行出错(不阻止工具调用) |
| 2 | 阻止工具调用 |
案例 3:敏感文件保护(PreToolUse)
阻止 Claude Code 读取或修改包含密钥的文件。
{ "hooks": { "PreToolUse": [ { "matcher": "Read|Write|Edit", "hooks": [ { "type": "command", "command": "filepath=$(echo $CLAUDE_TOOL_INPUT | jq -r '.file_path // .filePath // empty') && if echo \"$filepath\" | grep -qiE '\\.env$|\\.env\\.local$|credentials|secrets|private.key|id_rsa'; then echo 'BLOCKED: 该文件可能包含敏感信息' >&2; exit 2; fi" } ] } ] } }案例 4:钉钉通知(Notification)
当 Claude Code 发出通知(如等待用户确认、任务完成)时,自动推送到钉钉群。
{ "hooks": { "Notification": [ { "matcher": "", "hooks": [ { "type": "command", "command": "msg=$(echo $CLAUDE_NOTIFICATION | jq -r '.message // \"Claude Code 通知\"') && curl -s -X POST 'https://oapi.dingtalk.com/robot/send?access_token=YOUR_TOKEN' -H 'Content-Type: application/json' -d \"{\\\"msgtype\\\": \\\"text\\\", \\\"text\\\": {\\\"content\\\": \\\"[Claude Code] $msg\\\"}}\"" } ] } ] } }案例 5:Slack 通知(Notification)
{ "hooks": { "Notification": [ { "matcher": "", "hooks": [ { "type": "command", "command": "msg=$(echo $CLAUDE_NOTIFICATION | jq -r '.message // \"Claude Code notification\"') && curl -s -X POST 'https://hooks.slack.com/services/YOUR/WEBHOOK/URL' -H 'Content-Type: application/json' -d \"{\\\"text\\\": \\\"$msg\\\"}\"" } ] } ] } }案例 6:Stop 后自动运行测试(Stop)
每当 Claude Code 完成一轮回复,自动运行相关的单元测试。
{ "hooks": { "Stop": [ { "matcher": "", "hooks": [ { "type": "command", "command": "changed=$(git diff --name-only HEAD 2>/dev/null | grep -E '\\.(ts|js)$' | head -5) && if [ -n \"$changed\" ]; then for f in $changed; do testfile=$(echo $f | sed 's/\\.ts$/.spec.ts/' | sed 's/\\.js$/.spec.js/'); if [ -f \"$testfile\" ]; then npx jest \"$testfile\" --passWithNoTests 2>&1 | tail -5; fi; done; fi" } ] } ] } }该 Hook 会检测 Git 变更中的 .ts/.js 文件,查找对应的 .spec.ts/.spec.js 测试文件并运行。只在有变更时触发,不会浪费时间。
案例 7:ESLint 自动修复(PostToolUse)
{ "hooks": { "PostToolUse": [ { "matcher": "Write|Edit", "hooks": [ { "type": "command", "command": "filepath=$(echo $CLAUDE_TOOL_INPUT | jq -r '.file_path // .filePath // empty') && if [ -n \"$filepath\" ] && echo \"$filepath\" | grep -qE '\\.(ts|tsx|js|jsx)$'; then npx eslint --fix \"$filepath\" 2>/dev/null || true; fi" } ] } ] } }三、完整配置示例
将上述多个 Hook 组合成完整的.claude/settings.json:
{ "hooks": { "PreToolUse": [ { "matcher": "Bash", "hooks": [ { "type": "command", "command": "cmd=$(echo $CLAUDE_TOOL_INPUT | jq -r '.command') && if echo \"$cmd\" | grep -qiE 'rm\\s+-rf\\s+/|drop\\s+table|drop\\s+database'; then echo 'BLOCKED' >&2; exit 2; fi" } ] }, { "matcher": "Read|Write|Edit", "hooks": [ { "type": "command", "command": "filepath=$(echo $CLAUDE_TOOL_INPUT | jq -r '.file_path // .filePath // empty') && if echo \"$filepath\" | grep -qiE '\\.env$|credentials|secrets'; then echo 'BLOCKED' >&2; exit 2; fi" } ] } ], "PostToolUse": [ { "matcher": "Write|Edit", "hooks": [ { "type": "command", "command": "filepath=$(echo $CLAUDE_TOOL_INPUT | jq -r '.file_path // .filePath // empty') && if [ -n \"$filepath\" ] && echo \"$filepath\" | grep -qE '\\.(ts|tsx|js|jsx|vue|css|json)$'; then npx prettier --write \"$filepath\" 2>/dev/null; fi" } ] } ], "Notification": [ { "matcher": "", "hooks": [ { "type": "command", "command": "msg=$(echo $CLAUDE_NOTIFICATION | jq -r '.message') && curl -s -X POST 'https://oapi.dingtalk.com/robot/send?access_token=YOUR_TOKEN' -H 'Content-Type: application/json' -d \"{\\\"msgtype\\\":\\\"text\\\",\\\"text\\\":{\\\"content\\\":\\\"[Claude Code] $msg\\\"}}\"" } ] } ], "Stop": [ { "matcher": "", "hooks": [ { "type": "command", "command": "changed=$(git diff --name-only HEAD 2>/dev/null | grep -E '\\.(ts|js)$' | head -5) && if [ -n \"$changed\" ]; then npx jest --passWithNoTests 2>&1 | tail -3; fi" } ] } ] } }四、调试 Hooks
4.1 查看 Hook 输入数据
开发 Hook 时,可以先将输入数据写入日志文件:
{ "matcher": "Write", "hooks": [ { "type": "command", "command": "echo \"$(date): TOOL_INPUT=$CLAUDE_TOOL_INPUT\" >> /tmp/claude-hooks.log" } ] }4.2 常见问题
- Hook 不触发:检查 matcher 是否正确匹配工具名称(区分大小写)
- 命令执行失败:确保命令中的路径是绝对路径或在 PATH 中
- jq 未安装:Hook 脚本依赖
jq解析 JSON,需要提前安装 - 退出码误用:exit 2 会阻止工具调用,exit 1 只是报错但不阻止
五、最佳实践
- Hook 脚本要快:Hook 是同步执行的,耗时长的操作会阻塞 Claude Code
- 做好错误处理:Hook 失败不应该影响正常工作流,用
|| true兜底 - 按需启用:不要配置过多 Hook,每个 Hook 都有执行开销
- 团队共享:将
.claude/settings.json纳入版本控制,团队统一配置 - 渐进式添加:先从最有价值的 Hook(如自动格式化)开始,逐步扩展
总结
Hooks 是 Claude Code 自动化工作流的核心机制。通过合理配置 PreToolUse(安全守卫)、PostToolUse(自动格式化)、Notification(即时通知)和 Stop(自动测试)四类 Hook,你可以打造一个安全、高效、自动化的 AI 编程环境。
接口配置参考:https://9m8m.com/docs/