Claude Code Hooks 完全详解:让 AI 编程工具真正听你的话
摘要:Claude Code Hooks 是 Anthropic 为 Claude Code 设计的一套"生命周期钩子"机制,允许开发者在 AI 执行各类操作的关键节点插入自定义逻辑。本文从架构原理到实战落地,系统拆解 Hooks 的每一个细节,附 15+ 可直接使用的配置案例。
目录
- [什么是 Claude Code Hooks?](#什么是 Claude Code Hooks?)
- [Hooks 的架构设计](#Hooks 的架构设计)
- 完整生命周期事件图谱
- [四种 Hook 类型详解](#四种 Hook 类型详解)
- 配置文件与作用域
- [通信协议:stdin / stdout / 退出码](#通信协议:stdin / stdout / 退出码)
- [Matcher 匹配器完全指南](#Matcher 匹配器完全指南)
- 核心事件深度解析
- [实战案例:15+ 即用配置](#实战案例:15+ 即用配置)
- [异步 Hooks 与高级模式](#异步 Hooks 与高级模式)
- 工程化分层设计
- 安全考虑与避坑指南
- 调试方法
- 总结
1. 什么是 Claude Code Hooks?
Claude Code 是 Anthropic 推出的命令行 AI 编程工具。当你让它修改文件、执行命令、分析代码时,它实际上是在背后调用一系列"工具"(Tool)来完成这些操作。
Hooks(钩子) 就是在这些工具执行的前后,甚至会话的开始与结束,插入你自定义代码的机制。
┌──────────────────────────────────────────────────────────┐
│ 你的请求("帮我格式化所有 .ts 文件") │
│ ↓ │
│ Claude 分析请求 │
│ ↓ │
│ ← PreToolUse Hook 触发(你的代码在这里运行)→ │
│ ↓ │
│ Claude 调用 Edit / Write 工具 │
│ ↓ │
│ ← PostToolUse Hook 触发(你的代码在这里运行)→ │
│ ↓ │
│ Claude 完成响应 │
│ ↓ │
│ ← Stop Hook 触发(你的代码在这里运行)→ │
└──────────────────────────────────────────────────────────┘
Hooks 能做什么?
| 能力 | 描述 | 典型场景 |
|---|---|---|
| 🛡️ 拦截与阻止 | 在工具执行前判断是否允许 | 阻止删除生产配置文件 |
| 🔄 修改工具输入 | 在执行前修改参数 | 将危险命令替换为安全版本 |
| 📊 观测与日志 | 记录每一次操作 | 统计最常执行的命令 |
| 🔔 通知与提醒 | 触发外部通知 | 任务完成后发 Slack 消息 |
| ✅ 质量把关 | 执行后自动检查 | 文件修改后自动运行 ESLint |
| 🤖 触发子代理 | 启动新的 AI 子任务 | 代码变更后自动跑测试 |
2. Hooks 的架构设计
2.1 整体架构图
┌─────────────────────────────────────────────────────────────────────┐
│ Claude Code 生命周期 │
│ │
│ Session Start ─→ [SessionStart Hook] │
│ │ │
│ ↓ │
│ 用户输入 ─→ [UserPromptSubmit Hook] ─→ LLM 推理 │
│ │ │
│ ↓ │
│ ┌─────────── 工具执行管线 ───────────┐ │
│ │ │ │
│ │ [PreToolUse Hook] │ │
│ │ ↓ │ │
│ │ 工具实际执行(Bash/Edit/...) │ │
│ │ ↓ │ │
│ │ [PostToolUse Hook] │ │
│ │ │ │
│ └────────────────────────────────────┘ │
│ │ │
│ ↓ │
│ Claude 响应完成 ─→ [Stop Hook] ─→ 会话结束 ─→ [SessionEnd Hook] │
│ │
└─────────────────────────────────────────────────────────────────────┘
2.2 核心设计理念
Hooks 的通信采用**进程间通信(IPC)**模型,非常优雅:
- Claude Code 作为主进程,在关键事件点 fork 出 Hook 子进程
- 主进程通过 stdin 向子进程传递 JSON 格式的事件数据
- 子进程通过 stdout 返回 JSON 格式的决策结果
- 子进程的 退出码(exit code) 决定是否阻止主进程的操作
这种设计让 Hooks 可以用任何编程语言实现,只需能读取 stdin、写入 stdout 即可。
3. 完整生命周期事件图谱
Claude Code 提供了 27 个可用的 Hook 事件,覆盖从会话启动到文件变更的完整生命周期。
3.1 事件分类总览
会话级事件
├── SessionStart # 会话开始/恢复时
├── SessionEnd # 会话终止时
├── UserPromptSubmit # 用户提交提示前
├── UserPromptExpansion # 斜杠命令展开时
├── Stop # Claude 完成响应时(可阻止)
├── StopFailure # API 错误导致中止时
├── PreCompact # 上下文压缩前(可阻止)
└── PostCompact # 上下文压缩后
工具执行级事件
├── PreToolUse # 工具执行前(可阻止)⭐ 最常用
├── PermissionRequest # 权限对话框显示时(可阻止)
├── PermissionDenied # 自动模式拒绝权限时
├── PostToolUse # 工具执行成功后⭐ 最常用
└── PostToolUseFailure # 工具执行失败后
多智能体协作事件
├── SubagentStart # 子代理启动时
├── SubagentStop # 子代理完成时(可阻止)
├── TaskCreated # 任务创建时(可阻止)
├── TaskCompleted # 任务完成时(可阻止)
└── TeammateIdle # 队友空闲时(可阻止)
环境与工作区事件
├── InstructionsLoaded # CLAUDE.md 加载时
├── ConfigChange # 配置文件变更时(可阻止)
├── CwdChanged # 工作目录变更时
├── FileChanged # 监视文件变更时
├── WorktreeCreate # 工作树创建时(可阻止)
└── WorktreeRemove # 工作树移除时
MCP 交互事件
├── Elicitation # MCP 请求用户输入时(可阻止)
├── ElicitationResult # 用户响应 MCP 后(可阻止)
└── Notification # 发送通知时
3.2 可阻止事件汇总表
| 事件 | 退出码 2 的效果 |
|---|---|
PreToolUse |
阻止工具执行,stderr 内容反馈给 Claude |
UserPromptSubmit |
阻止提示处理 |
Stop |
阻止 Claude 停止,迫使其继续工作 |
SubagentStop |
阻止子代理结束 |
TaskCreated |
回滚任务创建 |
TaskCompleted |
阻止任务标记完成 |
ConfigChange |
阻止配置变更生效 |
PreCompact |
阻止上下文压缩 |
PermissionRequest |
拒绝权限申请 |
WorktreeCreate |
任何非零退出码都失败 |
4. 四种 Hook 类型详解
4.1 Command Hook(命令钩子)
最常用的类型,执行一个 shell 命令。
json
{
"type": "command",
"command": "/path/to/your-script.sh",
"shell": "bash",
"timeout": 600,
"async": false
}
| 字段 | 说明 | 默认值 |
|---|---|---|
command |
要执行的 shell 命令 | 必填 |
shell |
"bash" 或 "powershell" |
"bash" |
timeout |
超时秒数 | 600 |
async |
是否后台异步运行 | false |
asyncRewake |
异步失败时是否唤醒 Claude | false |
最小示例(Python 实现):
python
#!/usr/bin/env python3
import sys
import json
# 从 stdin 读取 Claude Code 传来的事件数据
hook_input = json.loads(sys.stdin.read())
tool_name = hook_input.get("tool_name", "")
tool_input = hook_input.get("tool_input", {})
# 例:检查 Bash 命令中是否包含危险操作
if tool_name == "Bash":
command = tool_input.get("command", "")
if "rm -rf /" in command:
# 退出码 2 = 阻止操作,stderr 内容会反馈给 Claude
print("危险命令已被拦截!", file=sys.stderr)
sys.exit(2)
# 退出码 0 = 允许操作,stdout 中的 JSON 会被解析
print(json.dumps({"continue": True}))
sys.exit(0)
4.2 HTTP Hook(HTTP 钩子)
将事件数据 POST 到一个 HTTP 端点,适合对接企业内部系统、审计服务等。
json
{
"type": "http",
"url": "http://localhost:8080/hooks/pre-tool-use",
"timeout": 30,
"headers": {
"Authorization": "Bearer $MY_TOKEN",
"Content-Type": "application/json"
},
"allowedEnvVars": ["MY_TOKEN"]
}
HTTP 响应处理规则:
| 响应状态 | 行为 |
|---|---|
2xx + 空 body |
成功,等同 exit 0 |
2xx + 纯文本 |
成功,文本作为上下文传给 Claude |
2xx + JSON |
成功,按 JSON Schema 解析 |
非 2xx |
非阻塞错误,继续执行 |
| 连接超时 | 非阻塞错误,继续执行 |
⚠️ 注意 :HTTP Hook 无法通过 HTTP 状态码触发阻止,若需阻止必须返回
2xx+ JSON body,并在 JSON 中设置decision: "block"。
4.3 Prompt Hook(提示钩子)
调用 LLM 对事件进行智能评估,适合需要语义理解的判断场景。
json
{
"type": "prompt",
"prompt": "判断以下命令是否安全:$ARGUMENTS\n如果安全返回 {\"ok\": true},否则返回 {\"ok\": false, \"reason\": \"理由\"}",
"model": "claude-haiku-4",
"timeout": 30
}
使用 $ARGUMENTS 占位符注入事件的 JSON 数据。
4.4 Agent Hook(代理钩子)⭐ 实验性
生成一个带工具访问权限的子代理,适合需要复杂推理或多步操作的场景。
json
{
"type": "agent",
"prompt": "运行项目测试套件(npm test),如果有测试失败,报告失败原因,不要修改任何文件。",
"timeout": 60
}
5. 配置文件与作用域
5.1 配置文件位置
作用域层级(优先级从高到低)
│
├── 托管策略(Managed Policy)
│ └── 组织级强制策略,企业管控用
│
├── 用户全局配置
│ └── ~/.claude/settings.json ← 对所有项目生效
│
├── 项目配置(可提交 Git)
│ └── .claude/settings.json ← 团队共享
│
├── 项目本地配置(不提交 Git)
│ └── .claude/settings.local.json ← 个人本地覆盖
│
└── 插件 / Skill
└── hooks/hooks.json ← 插件激活时生效
5.2 配置文件结构
json
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "/path/to/hook-script.sh",
"timeout": 30
}
]
}
],
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "npx prettier --write \"$(cat | jq -r '.tool_input.file_path')\""
}
]
}
]
}
}
配置结构说明:
hooks
└── <事件名> # Hook 事件(如 PreToolUse)
└── [ 规则组数组 ]
├── matcher # 匹配过滤器(可选)
└── hooks # 该规则下的 Hook 数组
├── type # Hook 类型
├── command # 执行命令
└── ... # 其他配置字段
5.3 选择配置位置的原则
| 放在哪里 | 适合放什么 |
|---|---|
| Managed(托管) | 安全底线:敏感路径禁访、高风险命令禁令、审计规则 |
| Project(项目) | 团队共识:提交前测试/lint、目录写保护、框架检查 |
| User(用户) | 个人偏好:格式化习惯、个人观测 Hook、环境初始化 |
| Local(本地) | 临时实验:本机特有工具、临时加强/放松约束 |
6. 通信协议:stdin / stdout / 退出码
这是理解 Hooks 运行机制最关键的部分。
6.1 输入:Claude Code → Hook(via stdin)
所有 Hook 事件都通过 stdin 接收以下公共字段:
json
{
"session_id": "abc123",
"transcript_path": "/path/to/conversation.jsonl",
"cwd": "/your/project/root",
"permission_mode": "default",
"hook_event_name": "PreToolUse",
"tool_name": "Bash",
"tool_input": {
"command": "npm test",
"description": "Run test suite"
},
"tool_use_id": "toolu_01ABC..."
}
6.2 输出:Hook → Claude Code(via stdout)
Hook 退出时,Claude Code 根据退出码决定行为:
退出码 0 ─→ 解析 stdout 中的 JSON,按 JSON 决策字段执行
退出码 2 ─→ 忽略 stdout,阻止当前操作,将 stderr 反馈给 Claude
其他 ─→ 显示"hook error"通知,但不阻止操作,继续执行
stdout JSON 输出格式(通用字段):
json
{
"continue": true, // false 时 Claude 完全停止处理
"stopReason": "...", // continue: false 时的说明消息
"suppressOutput": false, // 是否从调试日志隐藏 stdout
"systemMessage": "⚠️ 请注意:...", // 向用户展示的警告消息
"decision": "block", // 决策(block / allow)
"reason": "阻止原因" // 配合 decision 使用
}
6.3 PreToolUse 特有输出
json
{
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "deny", // allow / deny / ask / defer
"permissionDecisionReason": "生产数据库禁止写操作",
"updatedInput": { // 可修改工具的实际输入
"command": "npm run lint"
},
"additionalContext": "当前环境: production"
}
}
permissionDecision 四种取值:
| 值 | 含义 |
|---|---|
allow |
允许执行 |
deny |
拒绝执行 |
ask |
弹出确认对话框 |
defer |
让其他 Hook 决定(仅非交互模式有效) |
决策优先级 :deny > defer > ask > allow
7. Matcher 匹配器完全指南
Matcher 决定哪些工具调用会触发你的 Hook。
7.1 Matcher 三种模式
| 值 | 评估方式 | 示例 |
|---|---|---|
"*" 或空字符串 |
匹配所有 | 每次都触发 |
| 仅含字母/数字/下划线/竖线 | 精确匹配或 ` | ` 分隔的列表 |
| 含其他特殊字符 | JavaScript 正则表达式 | "^Notebook"、"mcp__memory__.*" |
7.2 工具名称参考
Bash # 执行 Shell 命令
Edit # 编辑文件(str_replace)
Write # 写入/创建文件
Read # 读取文件
Glob # 文件路径通配匹配
Grep # 内容搜索
Agent # 调用子代理
WebFetch # 抓取网页内容
WebSearch # 网络搜索
AskUserQuestion # 询问用户
ExitPlanMode # 退出计划模式
7.3 MCP 工具匹配
MCP 工具的命名规律:mcp__<服务器名>__<工具名>
mcp__memory__.* # 匹配 memory 服务器的所有工具
mcp__.*__write.* # 匹配任意服务器的写入工具
mcp__filesystem__.* # 匹配文件系统服务器
7.4 if 字段(精细过滤)
if 字段使用权限规则语法进行更细粒度的过滤,可以减少不必要的进程创建:
json
{
"type": "command",
"if": "Bash(rm *)",
"command": "/path/to/block-rm.sh"
}
Bash(rm *) 表示:只有当 Bash 工具的命令以 rm 开头时才触发这个 Hook。
8. 核心事件深度解析
8.1 SessionStart ------ 会话初始化
json
// stdin 输入(额外字段)
{
"source": "startup", // startup | resume | clear | compact
"model": "claude-sonnet-4-6",
"agent_type": "代理名称"
}
// stdout 输出(可注入额外上下文)
{
"hookSpecificOutput": {
"hookEventName": "SessionStart",
"additionalContext": "当前是生产环境,任何数据库操作都需要双重确认"
}
}
典型用途:初始化日志、检查环境变量、注入项目上下文。
持久化环境变量 (通过 CLAUDE_ENV_FILE):
bash
#!/bin/bash
if [ -n "$CLAUDE_ENV_FILE" ]; then
echo 'export NODE_ENV=production' >> "$CLAUDE_ENV_FILE"
echo 'export DEBUG_LOG=true' >> "$CLAUDE_ENV_FILE"
fi
exit 0
8.2 PreToolUse ------ 工具执行前(最关键)
PreToolUse 是使用最频繁的事件,支持拦截、修改、放行三种模式。
Bash 工具的完整输入结构:
json
{
"tool_name": "Bash",
"tool_input": {
"command": "npm test",
"description": "运行测试套件",
"timeout": 120000,
"run_in_background": false
},
"tool_use_id": "toolu_01ABC..."
}
Edit 工具的完整输入结构:
json
{
"tool_name": "Edit",
"tool_input": {
"file_path": "/project/src/app.ts",
"old_string": "const debug = true",
"new_string": "const debug = false"
}
}
三种决策模式示例:
python
import sys, json
data = json.loads(sys.stdin.read())
cmd = data.get("tool_input", {}).get("command", "")
# 模式 1:直接阻止
if "DROP TABLE" in cmd.upper():
print("禁止执行 DROP TABLE 命令", file=sys.stderr)
sys.exit(2)
# 模式 2:修改输入(悄悄改掉命令)
if cmd.startswith("grep "):
safer_cmd = cmd.replace("grep ", "rg ", 1)
result = {
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "allow",
"updatedInput": {"command": safer_cmd},
"additionalContext": "自动将 grep 替换为更快的 rg"
}
}
print(json.dumps(result))
sys.exit(0)
# 模式 3:直接放行
sys.exit(0)
8.3 PostToolUse ------ 工具执行后
PostToolUse 无法阻止操作(工具已执行),但可以触发后续行为。
json
// stdin 输入额外字段
{
"tool_name": "Write",
"tool_input": { "file_path": "src/app.ts" },
"tool_response": {
"success": true,
"content": "文件写入成功"
}
}
典型用途:格式化代码、运行 lint、git add、记录日志。
8.4 Stop ------ 响应完成时
Stop 事件在 Claude 完成响应时触发,可以阻止 Claude 停止,强制它继续工作。
json
// stdin 输入额外字段
{
"stop_hook_active": true, // 重要!当前是否已被 Stop Hook 触发过
"last_assistant_message": "我已完成了代码修改..."
}
⚠️ 防止无限循环 :必须检查
stop_hook_active,避免 Stop Hook 无限触发自身!
python
import sys, json
data = json.loads(sys.stdin.read())
# 防止无限循环
if data.get("stop_hook_active"):
sys.exit(0)
last_msg = data.get("last_assistant_message", "")
# 检查是否遗漏了测试
if "已完成" in last_msg and "测试" not in last_msg:
result = {
"decision": "block",
"reason": "请先运行测试套件,确认没有回归问题,再结束本轮对话"
}
print(json.dumps(result))
sys.exit(2)
sys.exit(0)
8.5 FileChanged ------ 文件监视
通过 matcher 设置监视的文件名列表,支持动态扩展监视范围:
json
{
"hooks": {
"FileChanged": [
{
"matcher": ".env|.envrc|.env.local",
"hooks": [
{
"type": "command",
"command": "echo '⚠️ 环境变量文件已变更,请检查安全性' | notify-send -",
"async": true
}
]
}
]
}
}
stdin 输入:
json
{
"file_path": "/absolute/path/.envrc",
"event": "change" // change | add | unlink
}
9. 实战案例:15+ 即用配置
以下所有案例均可直接复制到 .claude/settings.json 使用。
🛡️ 安全防护类
案例 1:阻止删除生产关键文件
json
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "CMD=$(cat | jq -r '.tool_input.command // empty') && if echo \"$CMD\" | grep -qEi '(rm\\s+-rf\\s+/|DROP\\s+TABLE|mkfs\\.|:\\(\\)\\{|chmod\\s+-R\\s+777\\s+/|dd\\s+if=.*of=/dev/)'; then echo \"BLOCKED: 危险命令被拦截: $CMD\" >&2; exit 2; fi"
}
]
}
]
}
}
案例 2:保护敏感文件不被修改
json
{
"hooks": {
"PreToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "FILE=$(cat | jq -r '.tool_input.file_path // empty') && if echo \"$FILE\" | grep -qE '(\\.env|\\.lock|secrets\\.yaml|credentials|id_rsa|\\.pem|\\.key)'; then echo \"BLOCKED: 禁止修改敏感文件: $FILE\" >&2; exit 2; fi"
}
]
}
]
}
}
案例 3:只读操作自动放行(减少权限提示)
json
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "CMD=$(cat | jq -r '.tool_input.command // empty') && if echo \"$CMD\" | grep -qE '^(ls|cat|head|tail|wc|find|grep|rg|git\\s+(status|log|diff|show|branch|blame)|echo|pwd|which|file|stat|du|df)\\b'; then echo '{\"hookSpecificOutput\":{\"hookEventName\":\"PreToolUse\",\"permissionDecision\":\"allow\"}}'; fi"
}
]
}
]
}
}
🎨 代码质量类
案例 4:Prettier 自动格式化
json
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "FILE=$(cat | jq -r '.tool_input.file_path // empty') && [ -n \"$FILE\" ] && npx prettier --write \"$FILE\" 2>/dev/null || true"
}
]
}
]
}
}
案例 5:ESLint 自动修复
json
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "FILE=$(cat | jq -r '.tool_input.file_path // empty') && [ -n \"$FILE\" ] && [[ \"$FILE\" =~ \\.(js|ts|jsx|tsx)$ ]] && npx eslint --fix \"$FILE\" 2>/dev/null || true"
}
]
}
]
}
}
案例 6:替换低效命令(命令优化建议)
json
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "CMD=$(cat | jq -r '.tool_input.command // empty') && if echo \"$CMD\" | grep -qE 'find .* -name.*\\|.*grep'; then echo '建议:使用 fd 替代 find | grep,例如: fd --type f \"pattern\"' >&2; exit 2; fi"
}
]
}
]
}
}
📋 Git 自动化类
案例 7:文件修改后自动 git add
json
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "FILE=$(cat | jq -r '.tool_input.file_path // empty') && [ -n \"$FILE\" ] && [ -f \"$FILE\" ] && git add \"$FILE\" 2>/dev/null || true"
}
]
}
]
}
}
案例 8:记录所有执行的 Bash 命令(审计日志)
json
{
"hooks": {
"PostToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "cat | jq -r '\"[\" + (now | strftime(\"%Y-%m-%d %H:%M:%S\")) + \"] \" + .tool_input.command' >> \"${CLAUDE_PROJECT_DIR:-.}/.claude/command_log.txt\""
}
]
}
]
}
}
🔔 通知类
案例 9:macOS 桌面通知
json
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "osascript -e 'display notification \"Claude Code 任务已完成\" with title \"Claude Code\" sound name \"Glass\"'"
}
]
}
]
}
}
案例 10:Slack Webhook 通知(适合长时间任务)
json
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "MSG=$(cat | jq -r '.last_assistant_message // \"任务完成\"' | head -c 200) && curl -s -X POST \"$SLACK_WEBHOOK_URL\" -H 'Content-Type: application/json' -d \"{\\\"text\\\": \\\"✅ Claude Code 完成:${MSG}\\\"}\""
}
]
}
]
}
}
🤖 智能验证类
案例 11:Stop Hook 任务完成度检查
json
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "prompt",
"prompt": "在结束前,请验证:\n1. 是否已运行测试?如果没有,先运行\n2. 是否更新了相关文档?\n3. 代码中是否还有 TODO 注释?\n如果有任何未完成项,请继续工作。\n重要:如果上下文中 stop_hook_active 为 true,直接完成不要再检查。"
}
]
}
]
}
}
案例 12:Agent Hook 自动运行测试
json
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "agent",
"prompt": "运行项目测试套件(npm test)。如果有测试失败,报告哪些测试失败及建议修复方案。不要修改任何文件,只汇报结果。"
}
]
}
]
}
}
📦 上下文管理类
案例 13:压缩后重新注入关键上下文
json
{
"hooks": {
"PostCompact": [
{
"hooks": [
{
"type": "prompt",
"command": "重要上下文(上下文压缩后重新注入):\n- 当前在 payments 微服务\n- 数据库:PostgreSQL 16 + pgvector\n- API 格式必须遵循 JSON:API 规范\n- 测试文件放在 __tests__/ 目录\n- 禁止使用 console.log,使用 Winston logger"
}
]
}
]
}
}
🌐 HTTP 集成类
案例 14:对接企业审计服务
json
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash|Edit|Write",
"hooks": [
{
"type": "http",
"url": "https://audit.yourcompany.com/claude-hooks",
"headers": {
"Authorization": "Bearer $AUDIT_TOKEN",
"X-Project": "$CLAUDE_PROJECT_DIR"
},
"allowedEnvVars": ["AUDIT_TOKEN"],
"timeout": 10
}
]
}
]
}
}
案例 15:SessionStart 注入动态环境信息
json
{
"hooks": {
"SessionStart": [
{
"hooks": [
{
"type": "command",
"command": "echo \"{\\\"hookSpecificOutput\\\": {\\\"hookEventName\\\": \\\"SessionStart\\\", \\\"additionalContext\\\": \\\"Git 分支: $(git branch --show-current 2>/dev/null || echo 'N/A'), Node 版本: $(node -v 2>/dev/null || echo 'N/A'), 当前时间: $(date)\\\"}}\"",
"shell": "bash"
}
]
}
]
}
}
10. 异步 Hooks 与高级模式
10.1 异步 Hook(async)
默认情况下,Hook 是同步的------Claude Code 会等待 Hook 执行完成。当任务较重时,可以设为异步:
json
{
"type": "command",
"command": "/path/to/slow-analysis.sh",
"async": true,
"timeout": 120
}
同步 vs 异步对比:
| 特性 | 同步 | 异步 |
|---|---|---|
| Claude 是否等待 | 是 | 否,立即继续 |
| 能否阻止操作 | 是 | 否 |
| 适合场景 | 安全检查、参数修改 | 日志记录、通知、测试 |
| 性能影响 | 有延迟 | 无延迟 |
10.2 asyncRewake 模式
asyncRewake: true 让异步 Hook 在退出码为 2 时"唤醒"Claude,适合后台任务完成后需要 Claude 介入的场景:
json
{
"hooks": {
"FileChanged": [
{
"matcher": "package.json",
"hooks": [
{
"type": "command",
"command": "npm install && npm test",
"async": true,
"asyncRewake": true,
"timeout": 120
}
]
}
]
}
}
asyncRewake 执行流程:
package.json 被修改
↓
FileChanged Hook 触发,后台运行 npm install && npm test
↓
Claude 继续其他工作(不等待)
↓
npm test 失败(退出码 2)
↓
Claude 被"唤醒",收到测试失败的 stderr 信息
↓
Claude 主动处理测试失败
10.3 环境变量参考
| 变量 | 说明 | 示例 |
|---|---|---|
$CLAUDE_PROJECT_DIR |
项目根目录 | /Users/you/myproject |
${CLAUDE_PLUGIN_ROOT} |
插件安装目录 | ~/.claude/plugins/myplugin |
${CLAUDE_PLUGIN_DATA} |
插件数据持久化目录 | 更新后仍保留 |
$CLAUDE_CODE_REMOTE |
是否是远程环境 | "true" |
$CLAUDE_ENV_FILE |
环境变量持久化文件路径 | 写入后下次会话生效 |
11. 工程化分层设计
当项目复杂度上升时,需要系统性地组织 Hooks。推荐以下四层架构:
┌────────────────────────────────────────────────────┐
│ 第四层:协作编排层 │
│ 触发 Scheduled Tasks、MCP 写外部系统、通知治理 │
│ 特点:异步处理,不阻塞主流程 │
├────────────────────────────────────────────────────┤
│ 第三层:观测审计层 │
│ 工具失败统计、权限请求统计、文件变化记录 │
│ 特点:轻量、异步入库、关注趋势 │
├────────────────────────────────────────────────────┤
│ 第二层:质量守门层 │
│ 格式化、lint、小范围测试、关键目录验证 │
│ 特点:快速执行,结果明确,团队共识 │
├────────────────────────────────────────────────────┤
│ 第一层:底线安全层 │
│ 敏感路径保护、高风险命令拦截、未授权外联限制 │
│ 特点:规则少而硬,放 Managed/Project 层 │
└────────────────────────────────────────────────────┘
落地推荐顺序:
- 先建底线安全 Hook(最先受益)
- 再建质量守门 Hook(提升代码质量)
- 补充观测统计 Hook(积累数据)
- 最后接外部协作 Hook(系统集成)
11.1 Hooks vs Subagents vs MCP 的分工
| 工具 | 强项 | 职责 |
|---|---|---|
| Hook | 靠近事件边界、快速判断、同步守门 | 感知事件 → 决定是否允许/路由 |
| Subagent | 独立上下文、深度探索、并行推理 | 多文件分析、复杂调试、深度研究 |
| MCP | 标准化外部系统接入 | 连接数据库、API、工单系统 |
12. 安全考虑与避坑指南
12.1 安全最佳实践
由于 Hooks 以完整用户权限运行,需要特别注意安全:
bash
# ✅ 正确:始终引用变量
FILE="$1"
cat "$FILE"
# ❌ 错误:未引用可能导致命令注入
cat $FILE
# ✅ 正确:使用绝对路径
SCRIPT="$CLAUDE_PROJECT_DIR/.claude/hooks/check.sh"
"$SCRIPT"
# ✅ 正确:阻止路径穿越
if echo "$FILE" | grep -q "\.\."; then
echo "路径穿越攻击被阻止" >&2
exit 2
fi
12.2 常见坑点
| 坑点 | 描述 | 解决方案 |
|---|---|---|
| Matcher 大小写 | "bash" 无效,必须 "Bash" |
工具名使用 PascalCase |
| 未读取 stdin | 直接写 jq 而不用 `cat |
` 管道 |
| Stop Hook 死循环 | Stop Hook 反复触发自身 | 检查 stop_hook_active 字段 |
| Shell 配置污染 | ~/.bashrc 的输出污染 JSON 解析 |
用 --norc 或清理 bashrc 的标准输出 |
| 超时时间不够 | 默认 600s,调用外部 API 需增大 | 设置合适的 timeout 值 |
| jq 未安装 | 解析 stdin 需要 jq |
提前安装 jq(brew install jq) |
| 异步 Hook 不能阻止 | async Hook 无法拦截操作 | 需要拦截的必须用同步 Hook |
13. 调试方法
13.1 使用 /hooks 命令
在 Claude Code 中输入 /hooks 可打开钩子浏览器,查看当前所有注册的 Hooks 及其来源(User/Project/Local/Plugin)。
13.2 启用 Debug 日志
bash
# 方式 1:启用调试模式
claude --debug
# 方式 2:指定日志路径
claude --debug-file /tmp/claude-debug.log
# 方式 3:详细日志级别
CLAUDE_CODE_DEBUG_LOG_LEVEL=verbose claude
日志位置:~/.claude/debug/<session-id>.txt
13.3 手动测试 Hook 脚本
bash
# 模拟 PreToolUse 输入,直接测试你的脚本
echo '{
"session_id": "test",
"hook_event_name": "PreToolUse",
"tool_name": "Bash",
"tool_input": {
"command": "rm -rf /important"
},
"cwd": "/project",
"permission_mode": "default"
}' | python3 .claude/hooks/check-command.py
echo "退出码: $?"
13.4 调试检查清单
□ Matcher 大小写是否正确(Bash 不是 bash)
□ command 路径是否正确(脚本是否有执行权限)
□ stdin 是否正确读取(cat | jq ...)
□ JSON 输出格式是否合法(jq . 验证)
□ 退出码是否符合预期(0/2/其他)
□ 脚本中是否有多余的 echo 污染 stdout
□ 超时设置是否合理
□ ~/.bashrc 是否有输出到 stdout 的语句
14. 总结
Claude Code Hooks 是一个设计精良的生命周期钩子系统,它的核心价值在于:
为什么 Hooks 重要?
没有 Hooks:AI 是一个黑盒,你只能信任或不信任
有了 Hooks:AI 是一个可观测、可控制、可扩展的协作伙伴
核心能力总结
| 能力维度 | 对应机制 | 价值 |
|---|---|---|
| 可控性 | PreToolUse 拦截 | 确保 AI 不做危险操作 |
| 可观测性 | PostToolUse 日志 | 了解 AI 在做什么 |
| 可扩展性 | 四种 Hook 类型 | 连接任何外部系统 |
| 可协作性 | SubagentStop/TaskCreated | 多 Agent 工作流 |
| 可调试性 | debug 日志 + /hooks 命令 | 快速定位问题 |
推荐的起步组合
- 🔔 Stop + 桌面通知 → 立即提升体验,任务完成知道通知
- 🛡️ PreToolUse + 敏感文件保护 → 安全底线,绝不侥幸
- 🎨 PostToolUse + Prettier → 消除最常见的手动干预
从这三个开始,随着使用深入再逐步添加更复杂的 Hooks,构建属于你团队的 AI 开发规范体系。
参考资料
- Claude Code Hooks 官方文档(中文)
- Claude Code Hooks 官方文档(英文)
- GitHub - anthropics/claude-code
- DeepWiki - Hook System 架构解析
作者:技术博客 | 最后更新:2026年4月
