Appearance
Hooks 是 Claude Code 中的确定性自动化机制——在 AI 工作流的特定节点(文件编辑前/后、会话结束等)自动执行你定义的 shell 命令,无论 AI 怎么决定都会执行。典型用途:编辑后自动 lint/格式化、阻止修改受保护文件、发送桌面通知、压缩后重新注入上下文。Hooks 配置在 ~/.claude/settings.json 中,支持按工具类型和文件路径精细过滤,并通过 stdin 接收工具调用详情,退出码可阻断或放行操作。
Hooks:自动化工作流
Hooks 是用户定义的 shell 命令,在 Claude Code 生命周期的特定节点自动执行。与 CLAUDE.md 指令不同,Hooks 是确定性的——它们保证某些动作一定会发生,而不是依赖 AI 决定要不要执行。
常见用途:
- 每次文件编辑后自动格式化代码
- Claude 需要你操作时发送桌面通知
- 阻止 Claude 修改受保护的文件
- 压缩上下文后重新注入关键信息
创建第一个 Hook
以桌面通知 Hook 为例:Claude 等待你输入时,你可以切换到其他任务,完成后收到通知。
第一步:添加 Hook 到设置
打开 ~/.claude/settings.json,添加 Notification hook:
macOS:
json
{
"hooks": {
"Notification": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "osascript -e 'display notification \"Claude Code needs your attention\" with title \"Claude Code\"'"
}
]
}
]
}
}Linux:
json
{
"hooks": {
"Notification": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "notify-send 'Claude Code' 'Claude Code needs your attention'"
}
]
}
]
}
}Windows(PowerShell):
json
{
"hooks": {
"Notification": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "powershell.exe -Command \"[System.Reflection.Assembly]::LoadWithPartialName('System.Windows.Forms'); [System.Windows.Forms.MessageBox]::Show('Claude Code needs your attention', 'Claude Code')\""
}
]
}
]
}
}第二步:验证配置
在 Claude Code 中运行 /hooks,确认 Notification 事件下有你的 Hook。
常见 Hook 配方
编辑后自动格式化
每次 Claude 编辑文件后自动运行 Prettier:
json
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "jq -r '.tool_input.file_path' | xargs npx prettier --write"
}
]
}
]
}
}阻止修改受保护文件
防止 Claude 修改 .env、package-lock.json 等敏感文件:
第一步:创建校验脚本 .claude/hooks/protect-files.sh:
bash
#!/bin/bash
INPUT=$(cat)
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')
PROTECTED_PATTERNS=(".env" "package-lock.json" ".git/")
for pattern in "${PROTECTED_PATTERNS[@]}"; do
if [[ "$FILE_PATH" == *"$pattern"* ]]; then
echo "Blocked: $FILE_PATH 匹配受保护模式 '$pattern'" >&2
exit 2
fi
done
exit 0第二步:赋予执行权限(macOS/Linux):
bash
chmod +x .claude/hooks/protect-files.sh第三步:注册 Hook:
json
{
"hooks": {
"PreToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/protect-files.sh"
}
]
}
]
}
}压缩后重新注入上下文
上下文压缩后,重新提醒 Claude 关键规则:
json
{
"hooks": {
"SessionStart": [
{
"matcher": "compact",
"hooks": [
{
"type": "command",
"command": "echo '提醒:使用 Bun 而不是 npm。提交前运行 bun test。当前 sprint:auth 重构。'"
}
]
}
]
}
}自动批准计划结束
不想每次计划结束都手动确认?用 PermissionRequest hook 自动批准:
json
{
"hooks": {
"PermissionRequest": [
{
"matcher": "ExitPlanMode",
"hooks": [
{
"type": "command",
"command": "echo '{\"hookSpecificOutput\": {\"hookEventName\": \"PermissionRequest\", \"decision\": {\"behavior\": \"allow\"}}}'"
}
]
}
]
}
}Hook 生命周期事件
| 事件 | 触发时机 |
|---|---|
SessionStart | 会话开始或恢复 |
UserPromptSubmit | 提交提示词、Claude 处理前 |
PreToolUse | 工具调用执行前(可阻止) |
PermissionRequest | 权限弹窗出现时 |
PermissionDenied | 工具调用被自动模式分类器拒绝时。返回 {retry: true} 告诉模型可以重试 |
PostToolUse | 工具调用成功后 |
PostToolUseFailure | 工具调用失败后 |
Notification | Claude Code 发送通知时 |
SubagentStart | 子代理被启动时 |
SubagentStop | 子代理完成时 |
TaskCreated | 任务通过 TaskCreate 被创建时 |
TaskCompleted | 任务被标记为完成时 |
Stop | Claude 完成响应时 |
StopFailure | 轮次因 API 错误结束时(输出和退出码被忽略) |
TeammateIdle | 代理团队成员即将进入空闲状态时 |
InstructionsLoaded | CLAUDE.md 或 .claude/rules/*.md 文件被加载到上下文时(会话开始时和懒加载时触发) |
ConfigChange | 会话期间配置文件发生变化时 |
CwdChanged | 工作目录改变时(如 Claude 执行 cd 命令)。适用于 direnv 等响应式环境管理 |
FileChanged | 被监视的文件在磁盘上发生变化时(matcher 指定要监视的文件名) |
WorktreeCreate | 通过 --worktree 或 isolation: "worktree" 创建 worktree 时。替换默认 git 行为 |
WorktreeRemove | 在会话退出或子代理完成时移除 worktree |
PreCompact | 上下文压缩前。Hook 可以通过退出码 2 或返回 {"decision":"block"} 阻止压缩 |
PostCompact | 上下文压缩完成后 |
Elicitation | MCP 服务器在工具调用期间请求用户输入时 |
ElicitationResult | 用户响应 MCP elicitation 后、响应发送回服务器前 |
SessionEnd | 会话结束时 |
Hook 如何工作
输入:Hook 从 stdin 接收 JSON 格式的事件数据:
json
{
"session_id": "abc123",
"cwd": "/Users/sarah/myproject",
"hook_event_name": "PreToolUse",
"tool_name": "Bash",
"tool_input": {
"command": "npm test"
}
}输出:通过退出码和 stdout/stderr 告诉 Claude Code 该做什么:
| 退出码 | 行为 |
|---|---|
0 | 操作继续。UserPromptSubmit 和 SessionStart 中 stdout 内容被添加到上下文 |
2 | 阻止操作。stderr 内容作为反馈发送给 Claude |
| 其他非零 | 操作继续,stderr 记录到日志(按 Ctrl+O 查看) |
阻止操作示例:
bash
#!/bin/bash
INPUT=$(cat)
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command')
if echo "$COMMAND" | grep -q "drop table"; then
echo "Blocked: 不允许执行删表操作" >&2
exit 2
fi
exit 0用 matcher 过滤 Hook
不加 matcher 时,Hook 在每次事件触发。用 matcher 缩小范围:
json
{
"PostToolUse": [
{
"matcher": "Edit|Write", // 只在编辑/写入后触发
"hooks": [...]
}
]
}不同事件的 matcher 过滤字段:
| 事件 | matcher 过滤字段 | 示例值 |
|---|---|---|
PreToolUse, PostToolUse, PostToolUseFailure, PermissionRequest, PermissionDenied | 工具名称 | Bash, `Edit |
SessionStart | 会话来源 | startup, resume, clear, compact |
SessionEnd | 结束原因 | clear, resume, logout, prompt_input_exit, other |
Notification | 通知类型 | permission_prompt, idle_prompt, auth_success, elicitation_dialog |
SubagentStart, SubagentStop | 代理类型 | Bash, Explore, Plan, 或自定义代理名称 |
PreCompact, PostCompact | 触发原因 | manual, auto |
ConfigChange | 配置来源 | user_settings, project_settings, local_settings, policy_settings, skills |
FileChanged | 要监视的文件名(字面量) | `.envrc |
StopFailure | 错误类型 | rate_limit, authentication_failed, billing_error, server_error, unknown |
InstructionsLoaded | 加载原因 | session_start, nested_traversal, path_glob_match, include, compact |
Elicitation, ElicitationResult | MCP 服务器名称 | 你配置的 MCP 服务器名称 |
UserPromptSubmit, Stop, TeammateIdle, TaskCreated, TaskCompleted, WorktreeCreate, WorktreeRemove, CwdChanged | 不支持 matcher | 每次事件触发时都会执行 |
MCP 工具用 mcp__服务器名__工具名 格式,如 mcp__github__.* 匹配所有 GitHub MCP 工具。
Hook 配置位置
| 位置 | 作用域 | 是否可共享 |
|---|---|---|
~/.claude/settings.json | 所有项目 | 否(本地) |
.claude/settings.json | 单个项目 | 是(可提交 git) |
.claude/settings.local.json | 单个项目 | 否(gitignore) |
高级 Hook 类型
Prompt-based Hooks
需要判断而非固定规则时,用 type: "prompt" 让 Claude 模型(默认 Haiku)来做决定:
json
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "prompt",
"prompt": "检查是否所有任务都已完成。如果没有,返回 {\"ok\": false, \"reason\": \"还需要做什么\"}。"
}
]
}
]
}
}返回 {"ok": false} 时,Claude 会根据 reason 继续工作。
Agent-based Hooks
需要读文件或运行命令来验证时,用 type: "agent" 创建能使用工具的验证代理:
json
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "agent",
"prompt": "验证所有单元测试通过。运行测试套件并检查结果。",
"timeout": 120
}
]
}
]
}
}HTTP Hooks
把事件数据 POST 到 HTTP 端点:
json
{
"hooks": {
"PostToolUse": [
{
"hooks": [
{
"type": "http",
"url": "http://localhost:8080/hooks/tool-use",
"headers": {
"Authorization": "Bearer $MY_TOKEN"
},
"allowedEnvVars": ["MY_TOKEN"]
}
]
}
]
}
}常见问题
Hook 不触发
- 运行
/hooks确认 Hook 出现在正确事件下 - 检查 matcher 是否和工具名完全匹配(区分大小写)
PermissionRequest在非交互模式(-p)不触发,改用PreToolUse
Hook 报错
- 手动测试脚本:
echo '{"tool_name":"Bash","tool_input":{"command":"ls"}}' | ./my-hook.sh - 脚本未执行:检查是否有执行权限(
chmod +x) - 找不到命令:用绝对路径或
$CLAUDE_PROJECT_DIR
Stop hook 无限循环 解析 stop_hook_active 字段,已触发时直接退出:
bash
#!/bin/bash
INPUT=$(cat)
if [ "$(echo "$INPUT" | jq -r '.stop_hook_active')" = "true" ]; then
exit 0
fi
# ... 其余 hook 逻辑相关资源
- Hooks 完整参考:完整的事件 schema 和高级功能
- 设置:在哪里配置 hooks