合集:Claude Code Skills 系列 · 中级篇(二)
前言
Skills 是"建议性的"——Claude 读取 Skill 内容后,决定如何执行。但有些操作需要强制执行、每次必做、不容遗忘,这就是 Hooks(钩子)的价值所在。
Hooks 是在 Claude Code 特定生命周期事件触发的确定性脚本,与 AI 的"理解和判断"无关——无论 AI 想不想,钩子都会执行。国内使用Claude Code 访问ccAiHub.com
一、Hooks 的核心概念
1.1 Hooks vs Skills
| 特性 | Skills | Hooks |
|---|---|---|
| 执行者 | AI(基于理解) | Shell 脚本(强制执行) |
| 可靠性 | 依赖 AI 理解正确 | 100% 确定性 |
| 适用场景 | 有判断力的任务 | 固定流程、安全卡点 |
| 配置方式 | SKILL.md 文件 | settings.json |
| 灵活性 | 高 | 低 |
简单说:需要判断的事交给 Skills,需要强制执行的事交给 Hooks。
1.2 生命周期事件
Claude Code 提供三个钩子挂载点:
用户输入请求
↓
Claude 决定调用工具
↓ ← PreToolUse 钩子(工具执行前)
工具执行
↓ ← PostToolUse 钩子(工具执行后)
Claude 完成回复
↓ ← Stop 钩子(对话结束时)
二、配置 Hooks
2.1 基本配置结构
在 .claude/settings.json 中配置:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "echo '即将执行 Bash 命令' >> .claude/hook.log"
}
]
}
],
"PostToolUse": [
{
"matcher": "Edit",
"hooks": [
{
"type": "command",
"command": "npx prettier --write \"$CLAUDE_TOOL_INPUT_FILE_PATH\" || true"
}
]
}
],
"Stop": [
{
"hooks": [
{
"type": "command",
"command": ".claude/hooks/post-session.sh || true"
}
]
}
]
}
}
2.2 Matcher 匹配规则
matcher 字段决定这个 Hook 对哪些工具触发:
// 匹配所有 Bash 命令
{ "matcher": "Bash" }
// 匹配特定 Bash 命令(包含 rm 的命令)
{ "matcher": "Bash(rm *)" }
// 匹配 Edit 工具(文件编辑)
{ "matcher": "Edit" }
// 匹配 Write 工具(文件写入)
{ "matcher": "Write" }
// 无 matcher = 匹配所有工具
{ "hooks": [...] }
2.3 可用的环境变量
在钩子脚本中,Claude Code 会注入以下环境变量:
| 变量 | 内容 |
|---|---|
CLAUDE_TOOL_NAME | 工具名称(如 Bash, Edit) |
CLAUDE_TOOL_INPUT | 工具的完整输入(JSON 格式) |
CLAUDE_TOOL_INPUT_COMMAND | Bash 工具的命令字符串 |
CLAUDE_TOOL_INPUT_FILE_PATH | Edit/Write 工具的文件路径 |
CLAUDE_TOOL_OUTPUT | 工具的输出结果(PostToolUse 可用) |
三、实战案例
3.1 案例一:危险命令拦截(PreToolUse)
目标:阻止 Claude 执行 git push --force 等危险命令。
.claude/hooks/block-dangerous.sh:
#!/bin/bash
# 危险命令拦截器
COMMAND="$CLAUDE_TOOL_INPUT_COMMAND"
# 定义危险模式
DANGEROUS_PATTERNS=(
"git push --force"
"git push -f"
"rm -rf /"
"rm -rf *"
"DROP TABLE"
"DROP DATABASE"
"> /dev/sda"
)
for pattern in "${DANGEROUS_PATTERNS[@]}"; do
if echo "$COMMAND" | grep -qi "$pattern"; then
echo "❌ 拦截了危险命令:$pattern"
echo "命令:$COMMAND"
exit 1 # 非零退出码 = 阻止执行
fi
done
exit 0 # 正常退出 = 允许执行
配置:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "bash .claude/hooks/block-dangerous.sh"
}
]
}
]
}
}
效果:当 Claude 试图执行 git push --force 时,脚本返回非零退出码,Claude Code 会拒绝执行并通知用户。
3.2 案例二:自动格式化(PostToolUse)
目标:Claude 每次编辑文件后,自动运行格式化。
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit",
"hooks": [
{
"type": "command",
"command": "bash .claude/hooks/auto-format.sh"
}
]
}
]
}
}
.claude/hooks/auto-format.sh:
#!/bin/bash
FILE="$CLAUDE_TOOL_INPUT_FILE_PATH"
if [ -z "$FILE" ]; then
exit 0
fi
# 根据文件类型选择格式化工具
case "$FILE" in
*.ts|*.tsx|*.js|*.jsx)
npx prettier --write "$FILE" 2>/dev/null || true
;;
*.py)
black "$FILE" 2>/dev/null || true
;;
*.go)
gofmt -w "$FILE" 2>/dev/null || true
;;
*.rs)
rustfmt "$FILE" 2>/dev/null || true
;;
esac
echo "✅ 已格式化:$FILE"
注意:使用 || true 防止格式化工具报错导致整个会话崩溃。
3.3 案例三:会话结束通知(Stop)
目标:Claude 完成任务后,发送桌面通知。
.claude/hooks/notify-done.sh:
#!/bin/bash
# 跨平台桌面通知
MESSAGE="Claude Code 任务完成"
TIMESTAMP=$(date "+%H:%M:%S")
case "$(uname -s)" in
Darwin)
# macOS
osascript -e "display notification \"$TIMESTAMP\" with title \"$MESSAGE\""
;;
Linux)
# Linux(需要 notify-send)
notify-send "$MESSAGE" "$TIMESTAMP" 2>/dev/null || true
;;
MINGW*|MSYS*|CYGWIN*)
# Windows
powershell -Command "
Add-Type -AssemblyName System.Windows.Forms
\$notification = New-Object System.Windows.Forms.NotifyIcon
\$notification.Icon = [System.Drawing.SystemIcons]::Information
\$notification.BalloonTipTitle = '$MESSAGE'
\$notification.BalloonTipText = '$TIMESTAMP'
\$notification.Visible = \$True
\$notification.ShowBalloonTip(3000)
" 2>/dev/null || true
;;
esac
配置:
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "bash .claude/hooks/notify-done.sh || true"
}
]
}
]
}
}
3.4 案例四:自动记录操作日志(PostToolUse)
目标:记录 Claude 对哪些文件做了什么操作,便于审计。
{
"hooks": {
"PostToolUse": [
{
"hooks": [
{
"type": "command",
"command": "bash .claude/hooks/audit-log.sh || true"
}
]
}
]
}
}
.claude/hooks/audit-log.sh:
#!/bin/bash
LOG_FILE=".claude/audit.log"
TIMESTAMP=$(date -Iseconds)
TOOL="$CLAUDE_TOOL_NAME"
INPUT="$CLAUDE_TOOL_INPUT"
# 记录到日志(只记录关键信息,不记录完整输出避免日志过大)
echo "$TIMESTAMP | $TOOL | $(echo "$INPUT" | jq -r '.command // .file_path // "N/A"' 2>/dev/null || echo 'N/A')" >> "$LOG_FILE"
审计日志示例:
2024-01-15T14:23:01+08:00 | Bash | git diff --staged
2024-01-15T14:23:05+08:00 | Edit | src/auth/login.ts
2024-01-15T14:23:12+08:00 | Bash | npx vitest run auth
2024-01-15T14:23:45+08:00 | Write | docs/api/openapi.yaml
四、通过 /hooks 命令配置
除了直接编辑 JSON,也可以用交互式界面:
# 在 Claude Code 中
/hooks
这会打开配置向导,按提示选择:
- 事件类型(PreToolUse / PostToolUse / Stop)
- 匹配规则
- 要执行的命令
五、Hooks 最佳实践
5.1 始终用 || true 保护非关键钩子
# 格式化失败不应该阻断工作流
npx prettier --write "$FILE" || true
# 通知失败不应该影响会话
notify-send "Done" || true
5.2 关键安全钩子:明确返回非零码
# 安全检查失败必须明确阻断
if is_dangerous_command "$COMMAND"; then
echo "❌ 危险命令已拦截"
exit 1 # 必须显式返回非零
fi
5.3 不要在 PostToolUse 中读取被编辑的文件
Claude Code 在 PostToolUse 触发时,文件已经写入磁盘,但如果你的钩子又去读这个文件,会产生额外的系统提示("文件已被外部修改"),干扰 Claude 的上下文。
正确做法:在 Stop 钩子中批量处理,而不是每次编辑后都读取。
5.4 钩子脚本放入版本控制
# 推荐放在项目中
.claude/hooks/
├── block-dangerous.sh
├── auto-format.sh
├── notify-done.sh
└── audit-log.sh
提交到 Git,让整个团队受益于相同的安全卡点。
六、本篇小结
| 钩子类型 | 典型用途 | 关键技巧 | ||
|---|---|---|---|---|
PreToolUse | 安全检查、访问控制 | 返回非零码阻断执行 | ||
PostToolUse | 自动格式化、日志记录 | 用 ` | true` 保护非关键操作 | |
Stop | 通知、清理、汇总 | 适合批量处理,避免频繁触发 |
下一篇是中级篇的最后一篇:团队协作与 Git 共享实践,学习如何把 Skills 和配置体系高效地在团队中落地。
系列导航
- 初级篇(一):什么是 Skills,为什么需要它
- 初级篇(二):从零创建你的第一个斜杠命令
- 初级篇(三):CLAUDE.md 与配置体系入门
- 中级篇(一):自动触发机制与工具权限精细控制
- 中级篇(二):Hooks 钩子与确定性自动化 ← 当前
- 中级篇(三):团队协作与 Git 共享实践
- 高级篇(一):CI/CD 集成与无头模式
- 高级篇(二):动态上下文注入与多文件 Skill
- 高级篇(三):多智能体编排与复杂工作流