Hooks:在 Claude Code 生命周期上挂触发器
原文:https://code.claude.com/docs/en/hooks 官方定位:User-defined shell commands, HTTP endpoints, LLM prompts, or MCP tools that execute automatically at specific points in Claude Code’s lifecycle.
🔥 影响力卡片
| 维度 | 数据 |
|---|---|
| 事件总数 | 22 个 + lifecycle 事件类(本机文档枚举:SessionStart, SessionEnd, UserPromptSubmit, UserPromptExpansion, PreToolUse, PostToolUse, PostToolUseFailure, PostToolBatch, PermissionRequest, PermissionDenied, Stop, StopFailure, SubagentStart, SubagentStop, TaskCreated, TaskCompleted, Notification, CwdChanged, FileChanged, WorktreeCreate, WorktreeRemove, PreCompact, PostCompact, InstructionsLoaded, ConfigChange, Elicitation, ElicitationResult, Setup) |
| 5 种 handler 类型 | command、http、mcp_tool、prompt(LLM yes/no)、agent(subagent verify) |
| v2.1.139 新增 | args: [] exec form(免 shell 直接 spawn)+ continueOnBlock(PostToolUse 拒绝原因喂回 Claude 继续 turn)+ hook 不再带 terminal access(避免污染 UI) |
| 安全配置 | allowManagedHooksOnly 让管理员限制 hook 来源;disableAllHooks 临时全关 |
| 已有第三方 hook 生态 | superpowers plugin 里有 hookify 等 plugin 已经在做”hook 配置即代码” |
🎯 为什么必读
1. 这是 Claude Code 里唯一”确定性”的扩展机制。
Skill 是 LLM 决定要不要调,subagent 是 LLM 决定要不要派,plugin 是 LLM 决定要不要用。Hook 不。Hook 是事件触发的确定性程序:你写了 PreToolUse Bash hook,就保证每个 Bash 调用前都跑你的脚本 — 无视 Claude 的判断。
2. 它是把 Claude 从”建议者”变成”被守门员看着的执行者”的关键。
PreToolUse可以阻止危险操作(rm -rf之前 hook 看一眼)PostToolUse可以事后审计(写文件后跑 lint)Stop可以强制继续(Claude 想停,hook 说”还没完”)UserPromptSubmit可以改写你的 prompt 或追加 context
3. v2.1.139 这一波改进让 hook 真正可用
args: []exec form → 路径里有空格也不会被 shell 拆;${CLAUDE_PROJECT_DIR}/hooks/check.sh不再用引号continueOnBlock→ PostToolUse 拒绝后,Claude 不再”卡死”,拒绝原因变成 context 让 Claude 自己改- hook 不再有 terminal access → 避免 hook 写 stdout 污染 UI
一句话总结
Hook = settings.json 里的一段 JSON,把”在某事件发生时跑某命令”做成可声明的;支持 5 种 handler(shell / HTTP / MCP / LLM prompt / subagent);可决定性地 allow / deny / 改写工具调用。
💎 金句墙
★ “Exit code 2 blocks; exit code 1 is non-blocking. JSON output on exit 0 enables structured control.” “退出码 2 阻止,退出码 1 不阻止。退出 0 时输出 JSON 能做结构化决策。” —— 🟢 整个 hook 协议的硬约束 — 必背
★ “Exec form (
args: []) recommended for path placeholders to avoid shell injection.” (v2.1.139) “exec 形式(args: [])推荐用于带路径变量的场景,避免 shell 注入。” —— 🟢 安全性建议升级 — 老格式还能用但新写法应该用 args:[]
★ “Hooks now run without terminal access” (v2.1.139 changelog) “Hook 现在跑时不带 terminal 访问权。” —— 🟢 修了一个隐蔽 bug:之前 hook 写 stdout 可能污染交互式 prompt 显示;现在彻底隔离
📋 核心精读
1. Hook 在 5 个位置可定义(优先级 enterprise > 其他)
| 位置 | 路径 | 范围 |
|---|---|---|
| Managed policy | enterprise IT 推 | 整组织(可强制) |
| 用户全局 | ~/.claude/settings.json | 你所有项目 |
| 项目共享 | .claude/settings.json | 项目所有人(进 git) |
| 项目本地 | .claude/settings.local.json | 仅本机(gitignored) |
| Plugin | <plugin>/hooks/hooks.json | plugin 启用时 |
| Skill / Agent | frontmatter hooks: 字段 | skill/agent active 时 |
2. 22+ 事件 — 真正高频的只有 6 个
| 事件 | 何时触发 | 是否能 block | 实际用途 |
|---|---|---|---|
PreToolUse ⭐ | 工具调用前 | ✅ deny / ask / 改写 | 守门 — 阻 rm -rf、改写命令 |
PostToolUse ⭐ | 工具调用后(成功) | ✅ + continueOnBlock(v2.1.139) | 写完文件自动 lint / format |
UserPromptSubmit ⭐ | 你按回车后 | ✅ block | 改写 prompt / 注入 context |
Stop | Claude 想结束 turn | ✅ block | 强制继续(“还没跑测试”) |
SessionStart | session 启动/恢复 | 仅注入 context | 给 session 注入 git status / branch 等 |
FileChanged | 外部改了文件 | ✅ | 监 .env、自动重载配置 |
其他事件多为 logging / side effect 用,不能阻断行为。
3. 第一个 hook — 阻止 rm -rf
.claude/hooks/block-rm.sh:
#!/bin/bash
COMMAND=$(jq -r '.tool_input.command' < /dev/stdin)
if echo "$COMMAND" | grep -q 'rm -rf'; then
jq -n '{
hookSpecificOutput: {
hookEventName: "PreToolUse",
permissionDecision: "deny",
permissionDecisionReason: "Destructive rm -rf blocked by hook"
}
}'
else
exit 0
fi
.claude/settings.json:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"if": "Bash(rm *)",
"command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/block-rm.sh",
"args": []
}
]
}
]
}
}
执行流程:
- Claude 想跑
rm -rf foo/ - PreToolUse fire → matcher
Bash命中 →if匹配Bash(rm *)命中 - hook 命令跑你的脚本 → 检测到
rm -rf→ 输出 JSON 拒绝 - Bash 调用被 deny,Claude 看到拒绝原因换方法
关键细节:
matcher在前(粗筛工具名)if在 hook 内部(细筛命令具体内容)- 退出 0 + JSON = 结构化决策;退出 2 = 直接阻
${CLAUDE_PROJECT_DIR}是项目根的占位符args: []触发 exec form(免 shell)
4. 第二个 hook — 写文件后自动 lint(最常用)
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "jq -r '.tool_input.file_path' | xargs npm run lint:fix",
"continueOnBlock": true
}
]
}
]
}
}
- 工具
Edit或Write调用后触发 - hook 从 stdin 读 JSON,提取
tool_input.file_path xargs把它喂给npm run lint:fix- 如果 lint 报错(退出 2),
continueOnBlock: true让 Claude 看到错误自己继续修,而不是卡死
5. 第三个 hook — SessionStart 注入 git 状态
.claude/hooks/inject-git.sh:
#!/bin/bash
branch=$(git branch --show-current 2>/dev/null || echo "(no git)")
uncommitted=$(git status --short 2>/dev/null | head -5)
jq -n --arg b "$branch" --arg u "$uncommitted" '{
hookSpecificOutput: {
hookEventName: "SessionStart",
additionalContext: "Branch: \($b)\nUncommitted:\n\($u)"
}
}'
{
"hooks": {
"SessionStart": [
{
"matcher": "startup|resume",
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/inject-git.sh",
"args": []
}
]
}
]
}
}
效果:每个 session 启动时,Claude 立刻知道当前 git 状态,不用问你或自己跑 git 命令。
6. 第四个 hook — HTTP webhook(给团队 / 给 Slack 推)
{
"hooks": {
"PostToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "http",
"url": "http://localhost:8080/audit/bash",
"timeout": 5,
"headers": {
"Authorization": "Bearer $MY_TOKEN"
},
"allowedEnvVars": ["MY_TOKEN"]
}
]
}
]
}
}
适用场景:
- 内部审计 — 把所有 Bash 调用日志发给团队 webhook
- 团队 Slack 通知 — “Claude just ran
npm publish” - 监控集成 — Sentry / Datadog 接收 hook 事件
注意:HTTP 200 不算 block;要 block 必须返回 200 + JSON 拒绝。
7. 第五个 hook — LLM prompt(让另一个 Claude 当审查员)
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "prompt",
"prompt": "Is this command safe to run on a production-adjacent dev machine? Command: $ARGUMENTS\n\nReturn JSON: {\"safe\": true|false, \"reason\": \"...\"}",
"model": "claude-haiku-4-5",
"timeout": 15
}
]
}
]
}
}
效果:每个 Bash 调用前,先让 Haiku 审一遍。Haiku 便宜,延迟 1-2 秒,但能拦掉 LLM 主线没看到的危险命令。
8. 第六个 hook — MCP tool(用第三方安全扫描器)
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "mcp_tool",
"server": "aikido-security",
"tool": "security_scan",
"input": { "file_path": "${tool_input.file_path}" }
}
]
}
]
}
}
效果:每次 Claude 写文件,自动调 Aikido security MCP server 扫一遍。这是 plugin marketplace 里第三方安全公司(42crunch / Aikido / Endor Labs)的真实卖点。
9. v2.1.139 引入的 2 个改进 — 关键细节
A. args: [] exec form
旧:
{
"type": "command",
"command": "node \"${CLAUDE_PLUGIN_ROOT}\"/scripts/check.js --fix"
}
新(推荐):
{
"type": "command",
"command": "node",
"args": ["${CLAUDE_PLUGIN_ROOT}/scripts/check.js", "--fix"]
}
优点:
- 路径里有空格不会被错切
- 不走 shell → 没有
$VAR注入风险 - 占位符
${...}不需要引号
B. continueOnBlock: true(PostToolUse 专用)
{
"type": "command",
"command": "./hooks/lint.sh",
"continueOnBlock": true
}
效果:hook 退出 2 时,拒绝原因被喂回 Claude,Claude 继续 turn 自己改,而不是 turn 停。对自动修复流程意义巨大。
10. 安全模型(必背)
| 风险 | 缓解 |
|---|---|
| Hook 脚本本身被改 | .claude/hooks/*.sh 进 git,代码审查 |
| Plugin hook 偷偷加新 deny rule | allowManagedHooksOnly 让管理员强制只允许 managed 来源 |
| hook 写文件污染项目 | hook 内部要做最小权限,不该改源码 |
团队级 hook 强制(PreCompact block 等) | 同上,managed settings |
| shell injection | 改用 args: [] exec form |
11. /hooks 菜单调试
输入 /hooks 进交互式菜单:
- 列出所有已注册 hook
- 按 source 过滤(User / Project / Local / Plugin / Session / Built-in)
- 看每个 hook 上次执行结果 / 错误
12. 通用输入字段(每个 hook 都收到)
通过 stdin JSON 收到:
{
"session_id": "...",
"transcript_path": "...",
"cwd": "...",
"permission_mode": "default",
"effort": { "level": "medium" },
"hook_event_name": "PreToolUse",
"agent_id": "...", // 如果是 subagent 触发
"agent_type": "..."
}
加上事件特定字段(如 tool_name、tool_input)。
${CLAUDE_EFFORT} 环境变量也能直接读 effort 级别 — 给”高 effort 时跑慢测试,低 effort 跳过”留口。
🟢 译者总评
1. Hook 是 “AI 工具进入企业” 的关键之一。
没有 hook,Claude Code 在 enterprise 场景就是个 black box — IT 不知道它会跑啥、写啥。有了 hook + managed settings,IT 能:
- 强制审计每个 Bash 调用
- 强制扫描每个 Write
- 强制限制只允许某些工具(
Skill(...)、Edit(...)规则) - 强制 SessionStart 注入合规上下文(如 PII handling rules)
没有这套,Anthropic 拿不到 Fortune 500。
2. 对独立开发者,hook 的”边际”价值反而不在守门
你一个人写代码,不需要太多 deny rules。但你可以用 hook 做:
- SessionStart 注入项目状态(branch、todos、TICK_LOG.md 进度)— 让 Claude 不用问你”现在做到哪了”
- PostToolUse 自动跑 lint / format / typecheck — 跑 CI 的 Claude 版
- Stop hook 强制 “未跑测试不许结束” — 防自己懒
- FileChanged 监
.env/package.json— 一改就提示 reload
3. v2.1.139 把 hook 推到了”日常可用”的阈值
之前 args: [] 没有,路径处理是大坑(WSL 用户尤其);现在干净了。
之前 PostToolUse block 等于 turn 卡死,Claude 看不到错误;现在有 continueOnBlock,hook 拒绝 = 启动 self-fix 循环。
如果你过去试过 hook 然后放弃,值得再试一次。
4. 一条建议:从 SessionStart + 注入 git 状态 入门
这是最低风险、最直接见效的 hook。配好后:
- 每次
claude启动,Claude 立刻知道 branch / uncommitted - 不用每天重复说”先 git status 看一下”
- 一次写好,所有项目共用(
~/.claude/settings.json全局)
写一次,享一年。
🔗 延伸阅读
- 同系列:
05-skills-system.md(skill frontmatter 里也能定义 hook)、04-plugins-marketplace.md(plugin 可以打包 hook) - 同系列:
07-mcp-ecosystem.md(type: mcp_toolhook 调 MCP server,衔接外部服务) - 官方:
/en/settings#settings-files— managed settings 结构 - 官方:
/en/permissions—if字段使用的 permission rule 语法 - 第三方:
anthropics/claude-plugins-official的hookifyplugin — 通过 hook 模板化做事
🔗 调研来源(可校验)
- 主原文:
https://code.claude.com/docs/en/hooks - changelog v2.1.139:
Added hook args: string[] field (exec form)+Added hook continueOnBlock config option for PostToolUse+hook writing to terminal could corrupt an on-screen interactive prompt; hooks now run without terminal access(本机raw/scan/CHANGELOG.md第 9-11 行 + 第 39 行) - 已知第三方 hook 生态:
anthropics/claude-plugins-officialmarketplace 里hookifyplugin - 安全审计场景:42Crunch / Aikido / Endor Labs 几个安全公司 plugin 都内嵌了 hook