什么是 Hooks
我们在使用 Claude Code 的过程中,经常会想:能不能在它执行某个操作之前或之后,自动跑一段我们自己的逻辑?
比如:
- 每次编辑文件后自动跑 lint
- 执行 bash 命令前做安全检查
- 任务完成后发一条通知到 Slack
这就是 Hooks 系统要解决的问题。Hooks 让我们可以在 Claude Code 的关键执行节点插入自定义脚本,实现工作流的自动化。
Hook 的类型
Claude Code 提供了以下几种 Hook 类型:
| Hook 类型 | 触发时机 | 典型用途 |
|---|---|---|
| PreToolUse | 工具执行之前 | 参数校验、安全检查、权限控制 |
| PostToolUse | 工具执行之后 | 自动格式化、测试、日志记录 |
| Notification | Claude Code 发送通知时 | 自定义通知渠道(Slack、邮件等) |
| Stop | Claude Code 停止响应时 | 清理工作、结果汇总 |
PreToolUse
在工具执行之前触发。我们可以用它来:
- 拦截危险操作
- 修改工具参数
- 记录操作日志
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hook": {
"type": "command",
"command": "python3 /path/to/validate-command.py"
}
}
]
}
}
PostToolUse
在工具执行之后触发。这是最常用的 Hook 类型:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hook": {
"type": "command",
"command": "npx eslint --fix $CLAUDE_FILE_PATH"
}
}
]
}
}
Notification
当 Claude Code 需要通知用户时触发:
{
"hooks": {
"Notification": [
{
"hook": {
"type": "command",
"command": "terminal-notifier -message '$CLAUDE_NOTIFICATION' -title 'Claude Code'"
}
}
]
}
}
Stop
当 Claude Code 完成任务停止响应时触发:
{
"hooks": {
"Stop": [
{
"hook": {
"type": "command",
"command": "bash /path/to/cleanup.sh"
}
}
]
}
}
配置方式
Hooks 在 .claude/settings.json 中配置。我们来看完整的配置结构:
{
"hooks": {
"PreToolUse": [
{
"matcher": "工具名称的正则表达式",
"hook": {
"type": "command",
"command": "要执行的命令",
"timeout": 10000
}
}
],
"PostToolUse": [],
"Notification": [],
"Stop": []
}
}
配置字段说明
| 字段 | 类型 | 说明 |
|---|---|---|
| matcher | string | 正则表达式,匹配工具名称(仅 PreToolUse 和 PostToolUse) |
| hook.type | string | 目前只支持 "command" |
| hook.command | string | 要执行的 shell 命令 |
| hook.timeout | number | 超时时间(毫秒),默认 10000 |
环境变量
Hook 脚本可以访问以下环境变量:
| 环境变量 | 说明 | 可用的 Hook 类型 |
|---|---|---|
| CLAUDE_TOOL_NAME | 当前工具名称 | PreToolUse, PostToolUse |
| CLAUDE_TOOL_INPUT | 工具输入参数(JSON) | PreToolUse, PostToolUse |
| CLAUDE_TOOL_OUTPUT | 工具输出结果(JSON) | PostToolUse |
| CLAUDE_FILE_PATH | 操作的文件路径 | PreToolUse, PostToolUse |
| CLAUDE_NOTIFICATION | 通知消息内容 | Notification |
| CLAUDE_SESSION_ID | 当前会话 ID | 所有类型 |
实战示例
示例 1:编辑后自动 Lint
这是最常见的需求——每次 Claude Code 编辑文件后,自动运行 ESLint 修复格式问题:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hook": {
"type": "command",
"command": "npx eslint --fix \"$CLAUDE_FILE_PATH\" 2>/dev/null || true"
}
}
]
}
}
注意最后的 || true——我们不希望 lint 失败阻塞 Claude Code 的工作流。
示例 2:编辑后自动运行测试
每次修改源代码后,自动运行相关的测试文件:
#!/bin/bash
# scripts/auto-test.sh
FILE_PATH="$CLAUDE_FILE_PATH"
# 只对 src 目录下的文件触发
if [[ "$FILE_PATH" != src/* ]]; then
exit 0
fi
# 推断测试文件路径
TEST_FILE="${FILE_PATH/src\//tests\/}"
TEST_FILE="${TEST_FILE/.ts/.test.ts}"
if [ -f "$TEST_FILE" ]; then
npx vitest run "$TEST_FILE" --reporter=verbose 2>&1
fi
配置:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hook": {
"type": "command",
"command": "bash scripts/auto-test.sh",
"timeout": 30000
}
}
]
}
}
示例 3:Bash 命令安全检查
在执行 bash 命令之前,检查是否包含危险操作:
#!/bin/bash
# scripts/safety-check.sh
INPUT="$CLAUDE_TOOL_INPUT"
# 解析命令内容
COMMAND=$(echo "$INPUT" | jq -r '.command // empty')
# 危险命令黑名单
DANGEROUS_PATTERNS=(
"rm -rf /"
"rm -rf ~"
"dd if="
"> /dev/sda"
"mkfs"
":(){:|:&};:"
)
for pattern in "${DANGEROUS_PATTERNS[@]}"; do
if echo "$COMMAND" | grep -q "$pattern"; then
echo "BLOCKED: 检测到危险命令: $pattern"
exit 1
fi
done
exit 0
当 Hook 脚本返回非零退出码时,Claude Code 会阻止该工具的执行。
示例 4:任务完成通知到 Slack
#!/bin/bash
# scripts/notify-slack.sh
WEBHOOK_URL="https://hooks.slack.com/services/YOUR/WEBHOOK/URL"
MESSAGE="$CLAUDE_NOTIFICATION"
curl -s -X POST "$WEBHOOK_URL" \
-H 'Content-type: application/json' \
-d "{\"text\": \"Claude Code: $MESSAGE\"}" \
> /dev/null 2>&1
{
"hooks": {
"Notification": [
{
"hook": {
"type": "command",
"command": "bash scripts/notify-slack.sh"
}
}
]
}
}
示例 5:自动格式化 + 类型检查组合
#!/bin/bash
# scripts/post-edit.sh
FILE="$CLAUDE_FILE_PATH"
# 只处理 TypeScript 文件
if [[ "$FILE" != *.ts && "$FILE" != *.tsx ]]; then
exit 0
fi
# 1. Prettier 格式化
npx prettier --write "$FILE" 2>/dev/null
# 2. ESLint 修复
npx eslint --fix "$FILE" 2>/dev/null
# 3. 类型检查(只报告,不阻塞)
npx tsc --noEmit --pretty 2>&1 | head -20
exit 0
Matcher 的高级用法
matcher 字段支持正则表达式,我们可以灵活匹配不同的工具:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hook": {
"type": "command",
"command": "bash scripts/check-bash.sh"
}
},
{
"matcher": "Write|Edit",
"hook": {
"type": "command",
"command": "bash scripts/check-write.sh"
}
},
{
"matcher": ".*",
"hook": {
"type": "command",
"command": "bash scripts/log-all-tools.sh"
}
}
]
}
}
常用的工具名称:
| 工具名称 | 说明 |
|---|---|
| Bash | 执行 shell 命令 |
| Read | 读取文件 |
| Write | 写入文件 |
| Edit | 编辑文件 |
| Glob | 文件搜索 |
| Grep | 内容搜索 |
| WebFetch | 网页抓取 |
调试 Hooks
Hook 不生效?这里有几个调试技巧:
1. 检查配置文件位置
确保 .claude/settings.json 在项目根目录下:
cat .claude/settings.json | jq '.hooks'
2. 添加日志输出
在 Hook 脚本中添加日志:
#!/bin/bash
# 记录所有 Hook 调用
echo "[$(date)] Tool: $CLAUDE_TOOL_NAME, File: $CLAUDE_FILE_PATH" >> /tmp/claude-hooks.log
3. 单独测试脚本
先手动设置环境变量,单独运行 Hook 脚本:
CLAUDE_TOOL_NAME="Write" \
CLAUDE_FILE_PATH="src/index.ts" \
bash scripts/post-edit.sh
4. 检查超时设置
如果 Hook 脚本执行时间较长,记得调大 timeout:
{
"hook": {
"type": "command",
"command": "bash scripts/slow-check.sh",
"timeout": 60000
}
}
完整配置示例
这是一个生产级的 Hooks 配置:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hook": {
"type": "command",
"command": "bash .claude/hooks/safety-check.sh",
"timeout": 5000
}
},
{
"matcher": "Write|Edit",
"hook": {
"type": "command",
"command": "bash .claude/hooks/pre-write-check.sh",
"timeout": 5000
}
}
],
"PostToolUse": [
{
"matcher": "Write|Edit",
"hook": {
"type": "command",
"command": "bash .claude/hooks/post-edit.sh",
"timeout": 30000
}
}
],
"Notification": [
{
"hook": {
"type": "command",
"command": "bash .claude/hooks/notify.sh"
}
}
],
"Stop": [
{
"hook": {
"type": "command",
"command": "bash .claude/hooks/on-stop.sh"
}
}
]
}
}
建议的目录结构:
.claude/
├── settings.json
└── hooks/
├── safety-check.sh
├── pre-write-check.sh
├── post-edit.sh
├── notify.sh
└── on-stop.sh
最佳实践
1. 保持 Hook 脚本轻量
Hook 会在每次工具调用时执行,如果脚本太慢会严重影响体验:
- 控制在 5 秒以内
- 避免网络请求(除非是通知类 Hook)
- 使用缓存减少重复计算
2. 优雅处理失败
#!/bin/bash
# 不要让 Hook 失败影响正常工作流
set +e # 不因错误退出
# 你的逻辑
npx eslint --fix "$CLAUDE_FILE_PATH" 2>/dev/null
# 总是返回成功(除非你确实想阻塞操作)
exit 0
3. 按文件类型过滤
不要对所有文件都执行 Hook,按需过滤:
FILE="$CLAUDE_FILE_PATH"
case "$FILE" in
*.ts|*.tsx) run_typescript_checks ;;
*.py) run_python_checks ;;
*.go) run_go_checks ;;
*) exit 0 ;;
esac
4. 团队共享 Hook 配置
把 Hook 脚本放在版本控制中,团队成员可以共享:
# .gitignore 中不要忽略 .claude/hooks/
# 但要忽略个人配置
.claude/settings.local.json
5. PreToolUse 慎用阻塞
PreToolUse 返回非零退出码会阻止工具执行。只在真正需要安全检查时使用阻塞,其他场景用 PostToolUse 做事后处理。
常见问题
Q: Hook 脚本的工作目录是什么?
A: 工作目录是 Claude Code 的当前工作目录,通常是项目根目录。
Q: 多个 Hook 的执行顺序是什么?
A: 按照配置数组中的顺序依次执行。如果某个 PreToolUse Hook 返回非零退出码,后续 Hook 不会执行,工具调用也会被阻止。
Q: Hook 可以修改工具的输入参数吗?
A: PreToolUse Hook 可以通过 stdout 输出 JSON 来修改工具参数。如果 Hook 没有输出,则使用原始参数。
Q: 如何在 Hook 中区分不同的文件类型?
A: 通过 $CLAUDE_FILE_PATH 环境变量获取文件路径,然后在脚本中判断扩展名。
Hooks 是 Claude Code 工程化的第一步。掌握了它,你就拥有了在 AI 工作流中注入自定义逻辑的能力——这是从”使用工具”到”驾驭工具”的关键跨越。
相关文章
用 Claude Code 构建自动化代码审查机器人
基于 SDK 和 Headless 模式打造 PR 自动审查流水线
🤖Claude Code从零编写 Claude Code Autopilot 模式:从想法到代码的完全自动化实现指南
手把手教你从零开始构建一个类似 oh-my-claudecode 的 Autopilot 自动执行系统,包含需求分析、任务规划、代码执行、QA 验证等完整流程的实现
🤖Claude Codeoh-my-claudecode Autopilot 模式详解:从想法到代码的完全自动化
深入解析 oh-my-claudecode 的 Autopilot 模式,了解如何实现从模糊想法到完整产品的全自动开发
评论
加载中...
评论
加载中...