前言
在使用Claude Code进行日常开发工作时,我们常常面临一些挑战:Claude有时会执行一些需要谨慎对待的操作(如删除文件、修改生产配置),而内置的权限提示并不总能覆盖所有自定义场景;同时,我们也希望在Claude完成某些操作后自动触发代码检查、测试运行、日志记录等流程。
Hooks正是为了解决这类问题而设计的。它是Claude Code提供的一套事件驱动钩子机制,允许开发者在智能体工作流的关键节点注入自定义逻辑,将原本需要人工干预或事后检查的步骤变成自动化、可编程的流程。
什么是Hooks

设计目标
Hooks的核心目标是让开发者对Claude Code的行为拥有精细的控制权,主要解决以下几类问题:
- 安全控制:在
Claude执行危险命令前进行拦截,例如阻止rm -rf等破坏性操作 - 权限自动化:根据规则自动批准或拒绝权限请求,减少频繁的人工确认
- 质量保障:在
Claude修改文件后自动运行代码风格检查、单元测试等 - 可观测性:记录每次工具调用日志、跟踪会话行为,满足审计需求
- 上下文注入:在会话启动或提示词提交时自动注入环境信息、项目上下文
工作原理
Hooks基于事件驱动模型。当Claude Code运行到特定生命周期节点时(如即将调用某工具、刚完成某操作、会话结束等),会触发对应的事件。如果开发者为该事件配置了Hook,Claude Code会将事件的JSON上下文数据传递给Hook处理程序。处理程序可以根据数据做出决策,通过退出码和JSON输出告知Claude Code是否允许该操作继续执行。
整个处理流程如下:
四种处理程序类型
Hooks支持四种不同类型的处理程序,适用于不同复杂度的场景:
| 类型 | 说明 | 适用场景 |
|---|---|---|
command | 执行Shell命令或脚本 | 命令验证、文件检查、日志记录 |
http | 向HTTP端点发送POST请求 | 调用外部服务、远程审批系统 |
prompt | 调用LLM进行单轮评估 | 需要语义理解的内容审查 |
agent | 启动一个具有工具访问权限的子智能体 | 需要读取文件、检查代码的复杂验证 |
Hook生命周期

事件总览
Hooks在Claude Code会话的不同阶段触发。以下是完整的事件列表:
| 事件名称 | 触发时机 | 支持阻断 |
|---|---|---|
SessionStart | 会话启动或恢复时 | 否 |
InstructionsLoaded | 加载CLAUDE.md或规则文件时 | 否 |
UserPromptSubmit | 用户提交提示词、Claude处理前 | 是 |
PreToolUse | 工具调用执行前 | 是 |
PermissionRequest | 出现权限确认对话框前 | 是 |
PostToolUse | 工具调用成功完成后 | 否(可提供反馈) |
PostToolUseFailure | 工具调用失败后 | 否(可提供反馈) |
Notification | Claude Code发送通知时 | 否 |
SubagentStart | 子智能体启动时 | 否 |
SubagentStop | 子智能体完成时 | 是 |
Stop | Claude完成当前响应时 | 是 |
TeammateIdle | 团队协作中某成员即将空闲时 | 是 |
TaskCompleted | 任务被标记为完成时 | 是 |
ConfigChange | 会话中配置文件发生变化时 | 是 |
WorktreeCreate | 创建Git Worktree时 | 是(替换默认行为) |
WorktreeRemove | 移除Git Worktree时 | 否 |
PreCompact | 执行上下文压缩前 | 否 |
SessionEnd | 会话结束时 | 否 |
生命周期顺序
在Agentic Loop(智能体循环)中,Hook事件的触发顺序如下:
配置方式
配置文件位置
Hook可以定义在不同的配置文件中,生效范围各不相同:
| 配置文件 | 生效范围 | 可提交到代码仓库 |
|---|---|---|
~/.claude/settings.json | 所有项目 | 否(仅本机) |
.claude/settings.json | 单个项目 | 是 |
.claude/settings.local.json | 单个项目 | 否(已.gitignore) |
| 企业托管策略配置 | 组织全局 | 是(管理员控制) |
Plugin hooks/hooks.json | 插件启用时 | 是(随插件分发) |
配置结构
Hook配置采用三层嵌套结构:
{
"hooks": {
"<EventName>": [
{
"matcher": "<regex pattern>",
"hooks": [
{
"type": "command",
"command": "your-script.sh"
}
]
}
]
}
}
- 第一层:选择要响应的事件,如
PreToolUse、Stop - 第二层:配置匹配器(
matcher),过滤何时触发 - 第三层:定义一个或多个处理程序(
Hook Handler)
匹配器规则
matcher字段是一个正则表达式,用于过滤事件触发条件。不同事件类型匹配的字段不同:
| 事件 | 匹配字段 | 示例值 |
|---|---|---|
PreToolUse、PostToolUse、PermissionRequest | 工具名称 | Bash、Edit|Write、mcp__.* |
SessionStart | 启动方式 | startup、resume、clear、compact |
SessionEnd | 结束原因 | clear、logout、other |
Notification | 通知类型 | permission_prompt、idle_prompt |
SubagentStart、SubagentStop | 智能体类型 | Bash、Explore、Plan |
ConfigChange | 配置来源 | user_settings、project_settings |
省略matcher或设置为"*"时,对所有事件触发。UserPromptSubmit、Stop等事件不支持matcher,每次都会触发。
处理程序公共字段
| 字段 | 必填 | 说明 |
|---|---|---|
type | 是 | "command"、"http"、"prompt"或"agent" |
timeout | 否 | 超时秒数,command默认600秒,prompt默认30秒,agent默认60秒 |
statusMessage | 否 | 执行时显示的自定义状态提示 |
command类型额外字段:
| 字段 | 必填 | 说明 |
|---|---|---|
command | 是 | 要执行的Shell命令 |
async | 否 | 设为true时后台运行,不阻塞Claude |
Hook输入与输出
公共输入字段
所有Hook事件都会收到以下JSON数据(通过标准输入stdin传入):
| 字段 | 说明 |
|---|---|
session_id | 当前会话标识符 |
transcript_path | 会话记录文件路径 |
cwd | 当前工作目录 |
permission_mode | 当前权限模式(default、plan、acceptEdits等) |
hook_event_name | 触发的事件名称 |
退出码语义
Shell命令的退出码决定了Claude Code的后续行为:
| 退出码 | 含义 |
|---|---|
0 | 成功,解析stdout中的JSON输出 |
2 | 阻断性错误:stderr内容反馈给Claude,触发阻断行为 |
| 其他非零 | 非阻断错误:仅在verbose模式显示stderr,继续执行 |
JSON输出字段
退出码0时,可在stdout输出JSON进行精细控制:
| 字段 | 默认值 | 说明 |
|---|---|---|
continue | true | 设为false时强制停止Claude处理 |
stopReason | 无 | continue为false时展示给用户的消息 |
suppressOutput | false | 设为true时隐藏verbose模式的输出 |
systemMessage | 无 | 展示给用户的警告消息 |
各事件决策控制
不同事件支持不同的决策控制方式:
| 事件 | 决策格式 | 可用值 |
|---|---|---|
PreToolUse | hookSpecificOutput.permissionDecision | allow/deny/ask |
PermissionRequest | hookSpecificOutput.decision.behavior | allow/deny |
UserPromptSubmit、PostToolUse、Stop | 顶层decision | "block" |
WorktreeCreate | stdout输出路径 | 新worktree的绝对路径 |
使用示例
阻止危险Shell命令
在PreToolUse中拦截包含rm -rf的命令,防止Claude意外删除文件。
在.claude/hooks/block-dangerous.sh中创建脚本:
#!/bin/bash
# 从 stdin 读取 JSON 输入,检查命令是否危险
COMMAND=$(cat | jq -r '.tool_input.command // empty')
if echo "$COMMAND" | grep -qE 'rm\s+-rf|rm\s+--no-preserve-root'; then
echo "Blocked: destructive command detected: $COMMAND" >&2
exit 2 # 退出码2触发阻断,stderr内容反馈给Claude
fi
exit 0 # 允许执行
在.claude/settings.json中配置:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/block-dangerous.sh"
}
]
}
]
}
}
文件修改后自动执行代码检查
每次Claude写入或编辑文件后,自动运行代码风格检查,并将结果反馈给Claude。
在.claude/hooks/lint-check.sh中创建脚本:
#!/bin/bash
INPUT=$(cat)
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')
# 只检查 TypeScript/JavaScript 文件
if [[ "$FILE_PATH" != *.ts && "$FILE_PATH" != *.js ]]; then
exit 0
fi
# 运行 ESLint
RESULT=$(npx eslint "$FILE_PATH" 2>&1)
EXIT_CODE=$?
if [ $EXIT_CODE -ne 0 ]; then
# decision: "block" 让 Claude 收到反馈并修正代码
echo "{\"decision\": \"block\", \"reason\": \"ESLint check failed:\\n$RESULT\"}"
exit 0
fi
exit 0
配置:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/lint-check.sh"
}
]
}
]
}
}
会话启动时注入环境上下文
在每次新会话启动时,自动将当前Git分支、最近提交、未提交变更等信息注入Claude的上下文中。
在.claude/hooks/session-context.sh中创建脚本:
#!/bin/bash
BRANCH=$(git branch --show-current 2>/dev/null || echo "unknown")
LAST_COMMIT=$(git log -1 --pretty=format:"%h %s" 2>/dev/null || echo "none")
DIRTY=$(git status --short 2>/dev/null | head -5)
# 通过 hookSpecificOutput 注入 Claude 可见的上下文
cat <<EOF
{
"hookSpecificOutput": {
"hookEventName": "SessionStart",
"additionalContext": "Current branch: $BRANCH\nLast commit: $LAST_COMMIT\nUncommitted changes:\n$DIRTY"
}
}
EOF
exit 0
配置(仅对新会话启动触发):
{
"hooks": {
"SessionStart": [
{
"matcher": "startup",
"hooks": [
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/session-context.sh"
}
]
}
]
}
}
自动评估任务是否真正完成
使用Prompt Hook,在Claude完成响应前,让LLM评估任务是否真正完成:
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "prompt",
"prompt": "You are evaluating whether Claude should stop working. Context: $ARGUMENTS\n\nCheck: 1) Are all user-requested tasks complete? 2) Are there any unresolved errors? 3) Is follow-up work needed?\n\nRespond with JSON: {\"ok\": true} to allow stopping, or {\"ok\": false, \"reason\": \"explanation\"} to continue.",
"timeout": 30
}
]
}
]
}
}
异步运行测试套件
文件修改后,在后台异步运行测试,不阻塞Claude继续工作,测试完成后将结果反馈到下一次对话轮次。
在.claude/hooks/run-tests-async.sh中创建脚本:
#!/bin/bash
INPUT=$(cat)
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')
# 仅对源文件运行测试
if [[ "$FILE_PATH" != *.go ]]; then
exit 0
fi
RESULT=$(go test ./... 2>&1)
EXIT_CODE=$?
if [ $EXIT_CODE -eq 0 ]; then
echo "{\"systemMessage\": \"All tests passed after editing $FILE_PATH\"}"
else
echo "{\"systemMessage\": \"Tests FAILED after editing $FILE_PATH:\\n$RESULT\"}"
fi
配置时设置async: true:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/run-tests-async.sh",
"async": true,
"timeout": 300
}
]
}
]
}
}
审计所有配置变更
记录会话中发生的所有配置文件变更,用于安全审计:
{
"hooks": {
"ConfigChange": [
{
"hooks": [
{
"type": "command",
"command": "bash -c 'echo \"[$(date)] Config changed: $(cat | jq -r .source) - $(cat | jq -r .file_path // empty)\" >> ~/claude-audit.log'"
}
]
}
]
}
}
管理与调试
使用/hooks菜单
在Claude Code中输入/hooks可打开交互式Hook管理界面,无需手动编辑JSON配置文件即可查看、添加和删除Hook。菜单中每个Hook会标注来源:
[User]:来自~/.claude/settings.json[Project]:来自.claude/settings.json[Local]:来自.claude/settings.local.json[Plugin]:来自插件,只读
调试方法
使用--debug参数启动Claude Code可查看详细的Hook执行日志:
claude --debug
输出示例:
[DEBUG] Executing hooks for PostToolUse:Write
[DEBUG] Found 1 hook matchers in settings
[DEBUG] Matched 1 hooks for query "Write"
[DEBUG] Executing hook command: .claude/hooks/lint-check.sh with timeout 600000ms
[DEBUG] Hook command completed with status 0: <stdout output>
在交互模式下,按Ctrl+O可切换verbose模式,实时查看Hook的执行进度。
禁用Hooks
临时禁用所有Hook而不删除配置,可在settings.json中添加:
{
"disableAllHooks": true
}
注意:通过托管策略配置的企业Hook不受此设置影响,只有在托管策略层级设置disableAllHooks才能禁用。
安全注意事项
Hook命令以当前用户的完整权限运行,编写时需遵守以下安全实践:
- 始终验证和清理输入:不要盲目信任
stdin中的JSON数据 - 必须引用Shell变量:使用
"$VAR"而非$VAR,防止路径和命令注入 - 阻止路径穿越:检查文件路径中是否存在
.. - 使用绝对路径:脚本引用使用完整路径,项目根目录用
"$CLAUDE_PROJECT_DIR" - 避免处理敏感文件:跳过
.env、.git/、密钥文件等
此外,Claude Code在启动时会对Hook配置拍摄快照,会话中途对配置文件的外部修改不会立即生效——需要通过/hooks菜单审核后才会应用,以防止恶意Hook注入。