Claude Code Hooks 自动化工作流指南:通知、格式化、文件保护、审计与环境变量
Hooks 是用户定义的 shell 命令,在 Claude Code 生命周期特定节点自动执行——保证操作必然发生,不依赖 LLM 选择。本文覆盖最常用场景:编辑后自动格式化(PostToolUse + Edit|Write)、保护敏感文件不被修改(PreToolUse + exit 2)、上下文压缩后重注入关键信息(SessionStart + compact matcher)、配置变更审计日志、目录切换时通过 CwdChanged 重载环境变量、自动批准特定权限请求。每个场景附可直接复制的 JSON 配置块,并包含 matcher 语法、输入输出格式、调试技巧和常见错误排查。
Hooks 让你对 Claude Code 的行为有确定性控制。与依赖 LLM 决定是否执行的 CLAUDE.md 指令不同,hooks 在匹配事件发生时必然运行。可以用 hooks 强制执行项目规则、自动化重复任务、集成现有工具链。
对需要判断力(而非确定性规则)的场景,可以使用 prompt-based hooks 或 agent-based hooks,让 Claude 模型评估条件。
其他扩展方式:skills(额外指令和可执行命令)、sub-agents(隔离上下文运行任务)、plugins(打包扩展跨项目共享)。
本文覆盖常用场景和入门步骤。完整事件 schema、JSON 输入输出格式、异步 hooks 和 MCP tool hooks 见 Hooks 参考。
配置第一个 Hook
创建 Hook 需在设置文件中添加 hooks 块。下面以“桌面通知”演示:当 Claude 等待你输入时发送通知,这样就不必一直盯着终端。
第一步:添加配置
打开 ~/.claude/settings.json,添加 Notification hook:
{
"hooks": {
"Notification": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "osascript -e 'display notification \"Claude Code needs your attention\" with title \"Claude Code\"'"
}
]
}
]
}
}
如果你的设置文件已有 hooks 键,将 Notification 作为已有事件键的兄弟添加,不要替换整个对象:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [{ "type": "command", "command": "jq -r '.tool_input.file_path' | xargs npx prettier --write" }]
}
],
"Notification": [
{
"matcher": "",
"hooks": [{ "type": "command", "command": "osascript -e 'display notification \"Claude Code needs your attention\" with title \"Claude Code\"'" }]
}
]
}
}
第二步:确认 hook 注册
在 CLI 中键入 /hooks 打开 hooks 浏览器,可以看到所有可用 hook 事件,已配置的事件旁边有计数。选择 Notification 确认新 hook 出现。
第三步:测试通知
按 Esc 返回 CLI,让 Claude 做需要权限的操作,然后切换离开终端,桌面应出现通知。
macOS 通知不显示?
osascript通过 Script Editor 发送通知。如果 Script Editor 没有通知权限,命令会静默失败。在终端运行osascript -e 'display notification "test"',然后打开 系统设置 → 通知,找到 Script Editor,开启允许通知。
常用自动化场景
每个场景包含可直接使用的配置块,添加到设置文件中。
获取输入时通知
使用 Notification 事件,在 Claude 等待输入或权限时发送桌面通知。空 matcher 匹配所有通知类型。要只针对特定事件,设置 matcher 为以下值之一:
| Matcher | 作用 |
|---|---|
permission_prompt |
Claude 等待你批准工具使用 |
idle_prompt |
Claude 完成工作等待你下一条指令 |
auth_success |
认证完成 |
elicitation_dialog |
MCP 服务器打开一个提醒表单 |
elicitation_complete |
MCP 提醒表单被提交或关闭 |
elicitation_response |
MCP 提醒回复发回给服务器 |
Linux 配置:
{
"hooks": {
"Notification": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "notify-send 'Claude Code' 'Claude Code needs your attention'"
}
]
}
]
}
}
Windows 配置:
{
"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')\""
}
]
}
]
}
}
编辑后自动格式化代码
用 PostToolUse 事件配合 Edit|Write matcher,只在文件编辑工具执行后触发。命令用 jq 提取文件路径传给 Prettier。
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "jq -r '.tool_input.file_path' | xargs npx prettier --write"
}
]
}
]
}
}
Bash 示例中用到
jq,安装:brew install jq(macOS)、apt-get install jq(Debian/Ubuntu)。
阻止编辑受保护文件
防止 Claude 修改 .env、package-lock.json、.git/ 等文件。用独立脚本检查路径,匹配则 exit 2 拦截。
创建脚本 .claude/hooks/protect-files.sh:
#!/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 matches protected pattern '$pattern'" >&2
exit 2
fi
done
exit 0
赋予执行权限:
chmod +x .claude/hooks/protect-files.sh
注册 PreToolUse hook:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/protect-files.sh"
}
]
}
]
}
}
上下文压缩后重注入关键信息
SessionStart hook 配合 compact matcher,在每次上下文压缩后自动注入项目惯例或近期工作。stdout 输出被加入 Claude 上下文。
{
"hooks": {
"SessionStart": [
{
"matcher": "compact",
"hooks": [
{
"type": "command",
"command": "echo 'Reminder: use Bun, not npm. Run bun test before committing. Current sprint: auth refactor.'"
}
]
}
]
}
}
可以用动态命令替代 echo,如 git log --oneline -5。如需在每次会话启动时注入上下文(而非仅在压缩后),考虑用 CLAUDE.md。环境变量持久化见 CLAUDE_ENV_FILE 参考。
记录配置变更审计日志
ConfigChange 事件在外部进程或编辑器修改配置文件时触发,可记录变更用于合规或拦截。
{
"hooks": {
"ConfigChange": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "jq -c '{timestamp: now | todate, source: .source, file: .file_path}' >> ~/claude-config-audit.log"
}
]
}
]
}
}
matcher 过滤配置类型:user_settings、project_settings、local_settings、policy_settings、skills。exit 2 或返回 {"decision": "block"} 可阻止变更。详见 ConfigChange 参考。
目录切换时重载环境变量
SessionStart + CwdChanged 配合 direnv 让 Claude 的 Bash 工具感知目录环境变量。写入 CLAUDE_ENV_FILE 的值会作为每条 Bash 命令的前置脚本执行。
{
"hooks": {
"SessionStart": [
{
"hooks": [
{
"type": "command",
"command": "direnv export bash > \"$CLAUDE_ENV_FILE\""
}
]
}
],
"CwdChanged": [
{
"hooks": [
{
"type": "command",
"command": "direnv export bash > \"$CLAUDE_ENV_FILE\""
}
]
}
]
}
}
先在各目录执行 direnv allow 。如果用 devbox 或 nix,替换 direnv export bash 为 devbox shellenv 或 devbox global shellenv。
若要监控特定文件变更(而非仅目录切换),用 FileChanged 事件:
{
"hooks": {
"FileChanged": [
{
"matcher": ".envrc|.env",
"hooks": [
{
"type": "command",
"command": "direnv export bash > \"$CLAUDE_ENV_FILE\""
}
]
}
]
}
}
自动批准特定权限请求
跳过总是允许的工具权限对话框。返回 JSON 写明 "behavior": "allow",matcher 精确限定范围防止过度授权。
{
"hooks": {
"PermissionRequest": [
{
"matcher": "ExitPlanMode",
"hooks": [
{
"type": "command",
"command": "echo '{\"hookSpecificOutput\": {\"hookEventName\": \"PermissionRequest\", \"decision\": {\"behavior\": \"allow\"}}}'"
}
]
}
]
}
}
若要同时切换权限模式,返回 JSON 中包含 updatedPermissions 数组:
{
"hookSpecificOutput": {
"hookEventName": "PermissionRequest",
"decision": {
"behavior": "allow",
"updatedPermissions": [
{ "type": "setMode", "mode": "acceptEdits", "destination": "session" }
]
}
}
}
bypassPermissions模式仅当会话启动时已启用该模式时才可用(通过--dangerously-skip-permissions等),且未被permissions.disableBypassPermissionsMode禁用。它不会被持久化为defaultMode。
Hooks 工作原理
Hook 事件在 Claude Code 生命周期特定点触发。事件触发时,所有匹配的 hooks 并行执行,相同命令自动去重。
事件列表
| 事件 | 触发时机 |
|---|---|
SessionStart |
会话开始或恢复 |
Setup |
使用 --init-only、--init 或 --maintenance 启动时,用于 CI 或脚本的一次性准备 |
UserPromptSubmit |
用户提交 prompt 后、Claude 处理前 |
UserPromptExpansion |
用户键入的命令展开为 prompt 后、到达 Claude 前 |
PreToolUse |
工具调用执行前,可以拦截 |
PermissionRequest |
权限对话框弹出时 |
PermissionDenied |
自动模式分类器拒绝工具调用后 |
PostToolUse |
工具调用成功后 |
PostToolUseFailure |
工具调用失败后 |
PostToolBatch |
一批并行工具调用全部解析后、下一次模型调用前 |
Notification |
Claude Code 发出通知时 |
SubagentStart |
子代理被创建时 |
SubagentStop |
子代理完成时 |
TaskCreated |
通过 TaskCreate 创建任务时 |
TaskCompleted |
任务标记为完成时 |
Stop |
Claude 完成响应时 |
StopFailure |
本次结束因 API 错误终止时 |
TeammateIdle |
agent team 同事即将闲置时 |
InstructionsLoaded |
CLAUDE.md 或 .claude/rules/*.md 加载到上下文时 |
ConfigChange |
配置文件在会话中变更时 |
CwdChanged |
工作目录改变时(如 cd 命令) |
FileChanged |
被监测的文件在磁盘上变更时 |
WorktreeCreate |
通过 --worktree 或 isolation: "worktree" 创建工作树时 |
WorktreeRemove |
工作树被移除时(会话结束或子代理完成) |
PreCompact |
上下文压缩前 |
PostCompact |
上下文压缩完成后 |
Elicitation |
MCP 服务器在工具调用中请求用户输入时 |
ElicitationResult |
用户响应 MCP 提醒后、回复发回服务器前 |
SessionEnd |
会话终止时 |
Hook 类型
"type": "command":运行 shell 命令"type": "http":POST 事件数据到 URL"type": "mcp_tool":调用已连接 MCP 服务器的工具"type": "prompt":单轮 LLM 评估"type": "agent":多轮验证(实验性,可能变化)
多 Hook 结果合并
多个 hooks 匹配同一事件时,所有命令执行完成后再合并结果。一个 hook 返回 deny 不会阻止其他 sibling hook 执行。对于 PreToolUse 的决策,最严格的生效:deny > ask > allow。additionalContext 文本全部保留并传给 Claude。
输入输出机制
Hooks 通过 stdin 接收事件特定 JSON,通过 stdout/stderr 和退出码通信。
输入示例(PreToolUse):
{
"session_id": "abc123",
"cwd": "/Users/sarah/myproject",
"hook_event_name": "PreToolUse",
"tool_name": "Bash",
"tool_input": {
"command": "npm test"
}
}
输出通过退出码:
- 0:无异议,正常进行(非自动批准,权限流仍适用)
- 2:拦截操作,stderr 内容作为反馈传给 Claude
- 其他:操作继续,转录显示
<hook name> hook error注意
结构化 JSON 输出(exit 0 时输出到 stdout):
{
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "deny",
"permissionDecisionReason": "Use rg instead of grep for better performance"
}
}
permissionDecision 值:allow(跳过交互确认,但不覆盖 deny 规则)、deny、ask。defer 仅在非交互模式可用。
返回 "allow" 不会覆盖设置中的 deny 规则,包括企业托管 deny 列表。钩子只能收紧限制,不能放松。
UserPromptSubmit 事件不返回决定,而是通过 additionalContext 注入文本。
Matcher 过滤器
无 matcher 时钩子匹配事件所有实例。不同事件 matcher 过滤以下字段:
| 事件 | 过滤字段 | 示例值 |
|---|---|---|
PreToolUse, PostToolUse, PermissionRequest 等 |
工具名称 | Bash, Edit|Write, mcp__.* |
SessionStart |
启动方式 | startup, resume, clear, compact |
Setup |
CLI 标志 | init, maintenance |
SessionEnd |
结束原因 | clear, resume, logout 等 |
Notification |
通知类型 | permission_prompt, idle_prompt 等 |
ConfigChange |
配置来源 | user_settings, project_settings 等 |
FileChanged |
文字文件名( | 分隔) |
StopFailure |
错误类型 | rate_limit, authentication_failed 等 |
UserPromptExpansion |
命令名 | 技能或命令名称 |
Elicitation |
MCP 服务器名 | 你的 MCP 服务器名称 |
事件 UserPromptSubmit, PostToolBatch, Stop, TeammateIdle, TaskCreated, TaskCompleted, WorktreeCreate, WorktreeRemove, CwdChanged 不支持 matcher,每次事件都会触发。
用 if 字段同时过滤工具名称和参数(需要 Claude Code v2.1.85+):
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"if": "Bash(git *)",
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/check-git-policy.sh"
}
]
}
]
}
}
if 只对工具事件(PreToolUse, PostToolUse, PostToolUseFailure, PermissionRequest, PermissionDenied)有效,其他事件添加 if 会阻止钩子运行。
配置文件位置
| 位置 | 范围 | 可否共享 |
|---|---|---|
~/.claude/settings.json |
所有项目 | 本机 |
.claude/settings.json |
单项目 | 可提交到仓库 |
.claude/settings.local.json |
单项目 | gitignore 不共享 |
| 托管策略设置 | 组织范围 | 管理员控制 |
插件 hooks/hooks.json |
插件启用时 | 捆绑在插件中 |
| 技能或代理 frontmatter | 技能或代理激活时 | 在组件文件中定义 |
运行 /hooks 浏览所有配置的 hooks,按事件分组。在设置文件中设 "disableAllHooks": true 可禁用所有 hooks(托管设置中的 hooks 仍运行,除非也在那里设置)。
Prompt-based Hooks
对于需要判断力的决策,使用 type: "prompt" hooks。Claude Code 将你的 prompt 和输入数据发给 Claude 模型(默认 Haiku),返回 JSON 决定:{"ok": true} 或 {"ok": false, "reason": "..."}。
不同事件中 ok: false 的效果:
Stop,SubagentStop:reason反馈给 Claude 让它继续工作PreToolUse:工具调用被拒绝,reason作为工具错误返回给 ClaudePostToolUse,PostToolBatch,UserPromptSubmit,UserPromptExpansion:本次轮次结束,reason作为警告行显示在对话中
示例:Stop hook 检查任务是否完成,未完成则让 Claude 继续:
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "prompt",
"prompt": "Check if all tasks are complete. If not, respond with {\"ok\": false, \"reason\": \"what remains to be done\"}."
}
]
}
]
}
}
完整配置选项见 Prompt-based hooks 参考。
Agent-based Hooks
实验性功能,行为可能在将来版本中变更。生产环境优先使用 command hooks。
当需要读取文件或运行命令来验证条件时,用 type: "agent" hooks。子代理可以读文件、搜索代码、使用工具,然后返回决策。超时默认 60 秒,最多 50 个工具使用轮次。
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "agent",
"prompt": "Verify that all unit tests pass. Run the test suite and check the results. $ARGUMENTS",
"timeout": 120
}
]
}
]
}
}
HTTP Hooks
type: "http" hooks 将事件数据 POST 到 HTTP 端点,端点在响应体中返回结果。适合将逻辑放在 Web 服务器或云函数中。
{
"hooks": {
"PostToolUse": [
{
"hooks": [
{
"type": "http",
"url": "http://localhost:8080/hooks/tool-use",
"headers": {
"Authorization": "Bearer $MY_TOKEN"
},
"allowedEnvVars": ["MY_TOKEN"]
}
]
}
]
}
}
端点应返回 JSON 响应体,格式与 command hooks 相同。HTTP 状态码不能单独拦截,需响应体字段。
限制与故障排查
限制
- Command hooks 只能通过 stdout/stderr/退出码通信,不能触发
/命令或工具调用。HTTP hooks 通过响应体通信。 - 超时:command/http/mcp_tool 默认 10 分钟(
UserPromptSubmit时降为 30 秒),prompt 30 秒,agent 60 秒。可用timeout字段按钩子覆盖。 PostToolUse无法撤销已执行的工具操作。PermissionRequest在非交互模式(-p)下不触发,应使用PreToolUse。Stophooks 在 Claude 完成响应时触发(非仅任务完成),用户中断时不触发。API 错误触发StopFailure。- 多个
PreToolUsehooks 返回updatedInput修改工具参数时,最后一个完成的生效(并行非确定),避免多个钩子修改同一工具的输入。
Hooks 与权限模式
PreToolUse hooks 在权限模式检查前执行。返回 "deny" 即使在 bypassPermissions 模式下也会拦截。返回 "allow" 不会覆盖设置中的 deny 规则。
Hook 不触发
- 运行
/hooks检查钩子是否出现在正确事件下 - 确认 matcher 大小写精确匹配
- 非交互模式(
-p)下PermissionRequest不触发,改用PreToolUse
Hook 错误输出
- 脚本非零退出。手动测试:
echo '{"tool_name":"Bash","tool_input":{"command":"ls"}}' | ./my-hook.sh; echo $? - “command not found”:使用绝对路径或
$CLAUDE_PROJECT_DIR,或添加"args": []使用 exec 形式避免 shell 引用 - “jq: command not found”:安装 jq 或用 Python/Node.js 处理 JSON
- 脚本不可执行:
chmod +x ./my-hook.sh
/hooks 未显示配置的钩子
- 文件变更通常自动检测,如未出现稍等几秒或重启会话
- 验证 JSON 有效(不允许尾逗号或注释)
- 确认设置文件位置正确:
.claude/settings.json(项目),~/.claude/settings.json(全局)
Stop hook 达到阻塞上限
连续阻塞 8 次后 Claude Code 会覆盖 Stop hook。钩子脚本应检查 stop_hook_active 字段是否为 true,是则提前 exit 0:
#!/bin/bash
INPUT=$(cat)
if [ "$(echo "$INPUT" | jq -r '.stop_hook_active')" = "true" ]; then
exit 0
fi
# ... 其余钩子逻辑
如需更多迭代,用环境变量 CLAUDE_CODE_STOP_HOOK_BLOCK_CAP 提高上限。
JSON 验证失败
非交互 shell 可能因 shell profile 中的 echo 输出污染 JSON 输出。将 profile 中的 echo 包裹在交互式 shell 检查内:
if [[ $- == *i* ]]; then
echo "Shell ready"
fi
调试技巧
- 按
Ctrl+O打开转录视图:成功静默,阻断错误显示 stderr,非阻断错误显示<hook name> hook error注意加首行 stderr。 - 启动时
claude --debug-file /tmp/claude.log,然后tail -f /tmp/claude.log查看详情。会话中可用/debug启用日志并显示日志路径。
深入学习
- Hooks 参考:完整事件 schema、JSON 输出格式、异步 hooks、MCP tool hooks
- 安全考量:在共享或生产环境部署前务必阅读
- Bash 命令验证器示例:完整参考实现
常见问题
Hooks 和 CLAUDE.md 有什么区别,什么时候该用哪个?
CLAUDE.md 是静态指令,Claude 读取后“知道”要怎么做,但不保证每次都会执行。Hooks 是确定性的,事件触发时必然运行,不依赖 LLM 判断。需要“每次都要发生”的操作(格式化、通知、保护文件)用 Hooks;需要 Claude 理解项目规范用 CLAUDE.md。
Hook 命令可以访问哪些上下文信息?
通过 stdin 以 JSON 格式接收事件数据。不同事件包含不同字段,例如 PreToolUse 包含 tool_name 和 tool_input,SessionStart 包含 source(startup/resume/clear/compact)。用 jq 或脚本解析。完整字段列表见 Hooks 参考。
非交互模式(-p)下如何自动批准权限请求?
PermissionRequest hooks 在 -p 模式下不触发。应改用 PreToolUse hook,返回 permissionDecision: "allow" 来跳过交互确认。注意 allow 不能覆盖设置中的 deny 规则。