https://code.claude.com/docs/zh-CN/hooks-guide
Hooks介绍
1. Hooks 的原理是什么?
Hooks 是用户定义的自动化脚本或逻辑(可以是 Shell 命令、HTTP 端点、LLM 提示或子代理),它们嵌入在 Claude Code 的生命周期中。
- 核心机制:当 Claude Code 运行到生命周期的特定节点(如会话开始、工具调用前、任务完成时)时,会检查配置文件中是否定义了匹配的 Hooks。
- 数据交互 :
- 输入 :Claude Code 将当前事件的上下文(如工具名称、命令内容、文件路径等)序列化为 JSON 数据。
- 对于命令型 Hooks,JSON 通过
stdin传递。 - 对于 HTTP 型 Hooks,JSON 作为
POST请求体发送。
- 对于命令型 Hooks,JSON 通过
- 输出 :Hook 处理程序执行后,返回结果给 Claude Code。
- 控制流 :通过退出代码(Exit Code)或返回特定的 JSON 字段(如
permissionDecision: "deny"或decision: "block")来告诉 Claude Code 是允许继续 、阻止操作 还是修改输入。
- 控制流 :通过退出代码(Exit Code)或返回特定的 JSON 字段(如
- 输入 :Claude Code 将当前事件的上下文(如工具名称、命令内容、文件路径等)序列化为 JSON 数据。
- 目的 :用于实现安全验证(如禁止
rm -rf)、自动 linting、环境设置、审计日志记录或自定义工作流拦截。
2. 如何触发和执行?
Hooks 的触发和执行遵循以下流程:
A. 触发时机 (Lifecycle Events)
Hooks 在特定的生命周期事件发生时触发。常见的事件包括:
SessionStart: 会话开始时。UserPromptSubmit: 用户提交提示后,Claude 处理前。PreToolUse: 最常用 ,在工具(如 Bash, Write, Read)执行之前。可用于拦截和修改工具调用。PostToolUse: 工具成功执行后。Stop: Claude 准备结束回答时。- 其他:
PermissionRequest,SubagentStart,TaskCompleted等。
B. 匹配机制 (Matching)
当事件触发时,系统会检查配置文件中的 matcher(匹配器):
- 如果事件支持匹配器(如
PreToolUse),它会检查工具名称(如Bash,Write)是否匹配正则表达式。 - 如果不匹配,Hook 被跳过;如果匹配(或未定义匹配器),则执行 Hook。
C. 执行流程
- 事件发生 :例如,Claude 决定运行
Bash: rm -rf /tmp。 - 数据发送 :Claude 将包含
tool_name: "Bash"和tool_input: { command: "rm -rf /tmp" }的 JSON 发送给 Hook。 - Hook 运行 :
- Command Hook: 运行本地 Shell 脚本。
- HTTP Hook: 发送 HTTP POST 请求到指定 URL。
- Prompt/Agent Hook: 调用 LLM 或生成子代理进行判断。
- 结果处理 :
- 允许 :脚本退出码为
0且无阻止指令 -> Claude 继续执行原操作。 - 阻止 :脚本退出码为
2或返回 JSON{ "permissionDecision": "deny" }-> Claude 取消操作并报错。 - 修改 :返回 JSON 包含
updatedInput-> Claude 使用修改后的参数执行。
- 允许 :脚本退出码为
3. 如何写一个 Hooks?
编写 Hooks 主要分为两步:配置定义 和 实现逻辑。
第一步:配置文件 (settings.json)
在项目根目录的 .claude/settings.json 或用户全局配置 ~/.claude/settings.json 中定义。
基本结构示例(拦截危险的 Bash 命令):
json
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": ".claude/hooks/block-rm.sh"
}
]
}
]
}
}
PreToolUse: 事件类型。matcher: 过滤条件,这里只针对Bash工具。type: 可以是command(Shell),http,prompt,agent。command: 要执行的脚本路径。
第二步:实现脚本逻辑 (以 Command Hook 为例)
创建一个 Shell 脚本(如 .claude/hooks/block-rm.sh),使其可执行 (chmod +x)。
脚本逻辑模板:
- 读取输入 :从
stdin读取 JSON。 - 解析数据:提取需要的信息(如命令字符串)。
- 判断逻辑:根据业务规则判断是否允许。
- 返回结果 :
- 允许 :直接
exit 0。 - 阻止 :打印错误信息到
stderr并exit 2;或者 打印特定 JSON 到stdout并exit 0。
- 允许 :直接
示例脚本内容 (block-rm.sh):
bash
#!/bin/bash
# 从 stdin 读取 JSON 输入
INPUT=$(cat)
# 提取命令内容
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command')
# 检查是否包含危险命令
if echo "$COMMAND" | grep -q 'rm -rf'; then
# 方法 A: 使用退出码 2 阻止 (简单粗暴)
echo "Blocked: Destructive command detected!" >&2
exit 2
# 方法 B: 使用 JSON 输出阻止 (更灵活,可定制理由)
# jq -n '{
# hookSpecificOutput: {
# hookEventName: "PreToolUse",
# permissionDecision: "deny",
# permissionDecisionReason: "Destructive command blocked by hook"
# }
# }'
# exit 0
else
# 允许执行
exit 0
fi
高级用法提示
- 异步执行 :在配置中添加
"async": true,让 Hook 在后台运行(适用于耗时任务如运行测试套件),不会阻塞 Claude。 - 基于 LLM 的判断 :设置
"type": "prompt",让 Claude 自己判断是否允许某个操作(例如:"检查这个文件修改是否符合代码规范")。 - 环境变量 :脚本中可以使用
$CLAUDE_PROJECT_DIR来获取项目根目录,确保路径正确。
通过以上步骤,您就可以根据需求定制 Claude Code 的行为,增强安全性或自动化工作流。
Hook 加载机制
Claude Code 有两种 hook 配置方式:
1. 插件 Hook(推荐方式)
在插件目录下的 hooks/hooks.json 中定义:
插件目录/
└── hooks/
└── hooks.json ← Claude Code 启动时自动扫描发现
加载流程:
- Claude Code 启动时扫描所有已安装插件
- 检查每个插件是否存在
hooks/hooks.json - 将发现的 hooks 加载到当前会话
- 插件必须被启用才能生效
2. 用户级 Hook
直接在 ~/.claude/settings.json 中配置:
json
{
"PreToolUse": [...],
"Stop": [...]
}
特点:
- 无需包裹层,直接在顶层定义事件
- 无
description字段 - 立即生效(下次会话)
大模型如何知道有 hooks?
不是大模型"知道",是 Claude Code 运行时框架在拦截事件时执行 hook 逻辑:
用户发送消息
↓
Claude Code 拦截 [UserPromptSubmit hook 触发]
↓
执行 hook 配置的逻辑(prompt 或 command)
↓
根据 hook 输出决定如何处理
↓
大模型收到的是经过 hook 处理后的上下文
Hook 在框架层面执行,模型本身不知道有 hooks 存在。
生效条件
| 条件 | 说明 |
|---|---|
| 插件已启用 | 在 settings.json 的 plugins 列表中 |
| 会话启动 | Hook 在会话开始时加载 |
| 重启生效 | 修改 hooks 后需重启 Claude Code |
配置位置对比
| 方式 | 配置文件 | 格式 |
|---|---|---|
| 插件 Hook | 插件/hooks/hooks.json |
{"hooks": {...}} |
| 用户 Hook | ~/.claude/settings.json |
直接顶层配置 |
常见误区
- ❌ 不是用户手动定义的
- ❌ 不是写在某处配置文件的
- ✅ 由 Claude Code 运行时自动注入到 command hooks
可用场景
在所有 command hooks 中可直接使用:
json
{
"type": "command",
"command": "bash $CLAUDE_PLUGIN_ROOT/scripts/validate.sh"
}
实际值示例
假设你正在使用 everything-claude-code 插件:
CLAUDE_PLUGIN_ROOT = /Users/username/.claude/plugins/marketplaces/everything-claude-code
不同插件的 hook 运行时,CLAUDE_PLUGIN_ROOT 会指向各自插件目录。
相关的环境变量
| 变量 | 说明 |
|---|---|
$CLAUDE_PROJECT_DIR |
项目根目录路径 |
$CLAUDE_PLUGIN_ROOT |
插件目录路径 |
$CLAUDE_ENV_FILE |
SessionStart 专用:持久化环境变量的文件 |
$CLAUDE_CODE_REMOTE |
是否运行在远程上下文 |
为什么使用它?
可移植性 :使用 $CLAUDE_PLUGIN_ROOT 而不是硬编码绝对路径,让 hook 脚本能在不同环境下复用。
bash
# ✅ 好:可移植
bash $CLAUDE_PLUGIN_ROOT/scripts/validate.sh
# ❌ 差:硬编码路径
bash /Users/username/.claude/plugins/cache/claude-plugins-official/plugin-dev/55b58ec6e564/scripts/validate.sh