Claude Code 钩子详解:包裹智能体的确定性层
什么是 Claude Code 钩子? 钩子是用户自定义的 shell 命令(也包括 HTTP 端点、MCP 工具和模型提示词),Claude Code 会在生命周期的固定节点自动执行它们:工具调用之前、编辑之后、会话开始时,以及 Claude 回答结束时。1 CLAUDE.md 给模型的是它多半会遵守的指令,而钩子无论模型是否配合都会执行。在任意会话中输入 /hooks,即可查看全部生命周期事件以及各自挂载了什么。
多数开发者用两层控制来运行 Claude Code:权限约束智能体被允许做什么,CLAUDE.md 描述它应该做什么。钩子是第三层,也是唯一能保证任何事情的一层。下文将依次讲解:心智模型、当前文档中的每一个生命周期事件、精确的输入输出契约、配置方式、五个可用模式,以及一套取舍框架。所有 API 细节都已对照 2026年8月8日的官方钩子参考文档与指南核实过。这套机制迭代很快,因此当本文与参考文档冲突时,以参考文档为准。(初次接触 Claude Code?不妨先看 5 分钟上手或 Claude Code 入门路径。)
TL;DR: 钩子从 stdin 接收 JSON,通过退出码或 stdout 上的 JSON 作答。exit 0 表示放行,exit 2 表示拦截(限于支持拦截的事件),而 exit 1 这个 Unix 里约定俗成的失败码什么都拦不住——这是钩子最大的一个坑。2 配置写在 settings.json 中 PreToolUse、Stop 这类事件名之下,再用 matcher 过滤。凡是必须发生的事情交给钩子,凡是模型知道即可的事情交给 CLAUDE.md。
心智模型:为非确定性内核套上保证
编码智能体是一个概率系统。让它每次编辑后都跑一遍 Prettier,它多半会照做。但当改动看起来无关紧要、上下文变得很长,或者你的措辞被理解成另一个意思时,它可能就跳过这一步。CLAUDE.md、技能和提示词全都只是建议:质量很高,通常被采纳,却永远得不到保证。
钩子是包裹在这个内核外面的确定性外壳。指南开篇给了一个单行定义——“钩子是用户自定义的 shell 命令”——并把要点讲得很直白:钩子提供的是“确定性控制:某些动作总会发生,而不是依赖 LLM 自己选择去执行”。3(指南这一行低估了当前的实际能力:参考文档中更完整的定义已经加入了 HTTP 端点和 LLM 提示词,处理器还可以是 MCP 工具,详见下文的配置一节。)格式化器在每次编辑时触发。命令守卫会评估每一次 Bash 调用。完成门禁会检查每一次收尾。
这种强制是真的,不是摆设:PreToolUse 钩子在任何权限模式检查之前触发,所以一个返回 permissionDecision: "deny" 的钩子,即便在 bypassPermissions 模式下或带着 --dangerously-skip-permissions 运行,也照样能拦住工具。反过来则不成立——返回 "allow" 的钩子无法放宽设置里的 deny 规则。钩子只能把策略收得比权限更紧,绝不能把它放松。4
生命周期:全部钩子事件
截至 2026年8月8日,参考文档记录了 31 个钩子事件。1 它们分属三种节奏:每会话一次(SessionStart、SessionEnd)、每轮对话一次(UserPromptSubmit、Stop、StopFailure),以及智能体循环内每次工具调用都触发(PreToolUse、PostToolUse)。其余的则在特定条件下触发——配置变更、上下文压缩、子智能体、MCP 交互等。
| 事件 | 触发时机 | 一个真实用途 |
|---|---|---|
SessionStart |
会话开始或恢复时 | 把 git 分支和未关闭的 issue 注入为上下文 |
Setup |
--init-only,或 -p 模式下的 --init/--maintenance |
在智能体运行前于 CI 中安装依赖 |
UserPromptSubmit |
你提交提示词后、Claude 处理之前 | 追加当前日期;拒绝包含密钥的提示词 |
UserPromptExpansion |
输入的命令展开成提示词时 | 审计或否决技能与命令的展开结果 |
PreToolUse |
工具调用执行之前 | 拦截破坏性的 shell 命令 |
PermissionRequest |
弹出权限对话框时 | 自动批准可信命令,免去逐次确认 |
PermissionDenied |
自动模式分类器拒绝了某次工具调用 | 返回 retry: true,允许模型重试 |
PostToolUse |
工具调用成功之后 | 自动格式化每个被编辑的文件 |
PostToolUseFailure |
工具调用失败之后 | 记录失败命令以便排查 |
PostToolBatch |
一批并行工具调用之后、下一次模型调用之前 | 为智能体循环设检查点或直接中止 |
Notification |
Claude Code 发送通知时 | Claude 需要输入时弹出桌面提醒 |
MessageDisplay |
助手消息正文显示期间 | 在屏幕上做脱敏(仅影响显示,转录记录不变) |
SubagentStart |
子智能体被创建时 | 按智能体类型注入专属上下文 |
SubagentStop |
子智能体结束时 | 在结果返回前校验子智能体的输出 |
TaskCreated |
通过 TaskCreate 创建任务时 |
强制执行任务命名或范围规则 |
TaskCompleted |
任务被标记为完成时 | 在完成生效前核验验收标准 |
Stop |
Claude 回答结束时 | 完成门禁:测试不通过就不许收尾 |
StopFailure |
本轮因 API 错误而结束 | 对 rate_limit 或 billing_error 告警(仅记录,输出会被忽略) |
TeammateIdle |
智能体团队中的队友即将进入空闲 | 用队列让队友持续有活可干 |
InstructionsLoaded |
CLAUDE.md 或 .claude/rules/*.md 文件被载入上下文时 |
记录哪些指令进入了本次会话 |
ConfigChange |
会话中途配置文件发生变化 | 拦截未经许可的设置改动 |
CwdChanged |
工作目录发生变化 | 重新加载 direnv 式的环境 |
DirectoryAdded |
会话中途通过 /add-dir 或 SDK 的 register_repo_root 注册工作目录时(v2.1.219 及以上) |
该仓库一加入就立刻载入它的上下文 |
FileChanged |
被监视的文件在磁盘上发生变化 | .env 变更时刷新环境变量 |
WorktreeCreate |
通过 --worktree 或 isolation: "worktree" 创建 worktree 时 |
替换默认的 git worktree 准备流程 |
WorktreeRemove |
worktree 被移除时 | 在会话或子智能体退出时执行自定义清理 |
PreCompact |
上下文压缩之前 | 保存那些丢不起的状态 |
PostCompact |
压缩完成之后 | 重新注入关键上下文 |
Elicitation |
MCP 服务器请求用户输入时 | 在无头运行中自动填表 |
ElicitationResult |
你回答了 MCP 的 elicitation 之后 | 在响应返回前校验或覆盖它 |
SessionEnd |
会话终止时 | 归档日志、释放资源 |
其中绝大多数你都用不上。几乎每一套投入生产的配置都建立在五个事件之上:PreToolUse、PostToolUse、UserPromptSubmit、SessionStart 和 Stop。其余的存在,是为了你真正需要它们的那一天。
契约:输入 JSON,输出退出码或 JSON
命令型钩子从 stdin 接收 JSON,并通过退出码、stdout 和 stderr 作答。(HTTP 钩子以 POST 请求体接收同样的 JSON,并通过响应体作答。)2
每个事件都会送来一个公共信封——session_id、transcript_path、cwd 和 hook_event_name,多数事件还带 permission_mode——再加上各事件特有的字段。一个针对 Bash 命令的 PreToolUse 钩子收到的是:
{
"session_id": "abc123",
"transcript_path": "/home/user/.claude/projects/.../transcript.jsonl",
"cwd": "/home/user/my-project",
"permission_mode": "default",
"hook_event_name": "PreToolUse",
"tool_name": "Bash",
"tool_input": { "command": "npm test" }
}
其他事件只是换掉后半段:UserPromptSubmit 带 prompt,SessionStart 带 source(startup/resume/clear/compact/fork——第五个值随 v2.1.214 的分叉会话一起到来,从旧的四值列表里抄来的 source 匹配钩子会悄无声息地漏掉分叉),Stop 带 stop_hook_active 和 last_assistant_message。在子智能体内部触发的钩子还会额外收到 agent_id 和 agent_type。2
退出码
结果只有三种:2
- Exit 0 —— 成功。Claude Code 会解析 stdout 中的 JSON 输出字段。对多数事件而言,stdout 只进调试日志;但对
UserPromptSubmit、UserPromptExpansion和SessionStart,纯文本 stdout 会作为 Claude 可见的上下文加入。 - Exit 2 —— 拦截性错误。stdout(连同其中的任何 JSON)被忽略,stderr 作为错误信息回传给 Claude。“拦截”具体意味着什么,取决于事件本身。
- 其他任何退出码 —— 非拦截性错误。转录记录中会出现一条
<hook name> hook error提示,执行继续。
最后这条值得加粗:exit 1 什么都拦不住。 文档对此有直接警告——尽管 1 是 Unix 中约定俗成的失败码,Claude Code 仍把 exit 1 当作非拦截性错误并继续往下走。执行策略的钩子必须用 exit 2。2
exit 2 在各事件上的效果如下:2
| 事件 | exit 2 的效果 |
|---|---|
PreToolUse |
拦截该次工具调用 |
PermissionRequest |
拒绝该项权限 |
UserPromptSubmit |
阻止处理并清除提示词 |
UserPromptExpansion |
阻止本次展开 |
Stop / SubagentStop |
阻止停止,对话继续 |
TeammateIdle |
阻止队友进入空闲 |
TaskCreated / TaskCompleted |
回滚创建 / 阻止完成 |
ConfigChange |
拦截该次配置变更(policy_settings 除外) |
PreCompact |
阻止上下文压缩 |
PostToolBatch |
在下一次模型调用前停住智能体循环 |
Elicitation / ElicitationResult |
拒绝该次 elicitation / 把响应变成拒答 |
WorktreeCreate |
任何非零退出码都会中止 worktree 的创建 |
其余事件都无法拦截。PostToolUse 和 PostToolUseFailure 会把 stderr 展示给 Claude(工具已经跑完了);SessionStart、Notification、SessionEnd、CwdChanged、FileChanged、PostCompact、SubagentStart 和 Setup 只把 stderr 展示给用户,而 DirectoryAdded 只把 stderr 送进调试日志;StopFailure、InstructionsLoaded、MessageDisplay 和 PermissionDenied 则完全忽略退出码——对 PermissionDenied 来说,唯一的抓手是 JSON 里的 retry: true。2
JSON 输出
如果你需要比“拦截或沉默”更细的控制,就以 exit 0 结束并向 stdout 打印一个 JSON 对象。先说一条铁律:要么退出码,要么 JSON,不能两者兼用——JSON 只在 exit 0 时被处理,exit 2 会把它丢弃。5
通用字段在所有事件上都生效:continue: false 会彻底停下 Claude(并向用户展示 stopReason),suppressOutput 把 stdout 从转录记录中隐藏,systemMessage 向用户显示一条警告,terminalSequence 则发出允许列表内的终端转义序列(桌面通知、窗口标题、响铃)。决策字段则因事件而异:5
| 事件 | 决策形态 | 关键字段 |
|---|---|---|
UserPromptSubmit、UserPromptExpansion、PostToolUse、PostToolUseFailure、PostToolBatch、Stop、SubagentStop、ConfigChange、PreCompact |
顶层 decision |
decision: "block" 加 reason(展示给 Claude)。想放行就省略 decision |
PreToolUse |
hookSpecificOutput |
permissionDecision:"allow" | "deny" | "ask" | "defer",另有 permissionDecisionReason,以及在执行前改写工具参数的 updatedInput |
PermissionRequest |
hookSpecificOutput |
decision.behavior:"allow" | "deny",可选 decision.updatedInput |
PermissionDenied |
hookSpecificOutput |
retry: true 告诉模型可以重试 |
PostToolUse |
hookSpecificOutput |
updatedToolOutput 替换工具的返回结果 |
Stop / SubagentStop |
hookSpecificOutput |
additionalContext:非错误性的反馈,让对话继续而不计为钩子错误 |
SessionStart、Setup、SubagentStart |
仅上下文 | additionalContext,以及 SessionStart 专有的 initialUserMessage、sessionTitle、watchPaths、reloadSkills。无法拦截 |
MessageDisplay |
hookSpecificOutput |
displayContent 只替换屏幕上的文字 |
Elicitation / ElicitationResult |
hookSpecificOutput |
action:"accept" | "decline" | "cancel",以及 content |
TeammateIdle、TaskCreated、TaskCompleted |
通用 continue |
continue: false 加 stopReason 会彻底终止队友或任务的流程(按事件的拦截仍是 exit 2) |
WorktreeCreate |
返回路径 | 命令型钩子把 worktree 路径打印到 stdout;HTTP 钩子返回 hookSpecificOutput.worktreePath;失败或缺少路径都会导致创建失败 |
WorktreeRemove、Notification、SessionEnd、PostCompact、InstructionsLoaded、StopFailure、CwdChanged、DirectoryAdded、FileChanged |
无 | 只有副作用 |
有两个细节最容易让人栽跟头。其一,PreToolUse 是顶层 decision 模式的例外:它历史上确实用顶层的 decision/reason,但这两个字段在该事件上已废弃("approve"/"block" 分别对应 "allow"/"deny"),请改用 hookSpecificOutput.permissionDecision。5 其二,当多个 PreToolUse 钩子给出不同判断时,优先级是 deny > defer > ask > allow——最严格的答案胜出。即便如此,也别指望这套平局规则,最好让每个决策都只有一个负责的钩子。5
配置:settings.json、matcher 与作用域
钩子配置分三层嵌套:选一个事件,加一个matcher 组来过滤触发时机,再定义一个或多个要运行的钩子处理器。6
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{ "type": "command", "command": "/path/to/lint-check.sh" }
]
}
]
}
}
把它放在哪里决定了作用域:~/.claude/settings.json 对你所有项目生效,.claude/settings.json 属于项目级且可以提交,.claude/settings.local.json 属于项目级但被 gitignore 排除;优先级沿用标准的设置层级——托管策略高于本地,本地高于项目,项目高于用户。9 钩子还可以随插件(hooks/hooks.json)以及技能或智能体的 frontmatter 一起分发,企业管理员也可以下发用户无法覆盖的托管钩子。6
matcher 按其字符构成来判定:"*"、"" 或省略 matcher 表示匹配一切;只包含字母、数字、_、-、空格、逗号和 | 的值是精确字符串或列表(Bash、Edit|Write);其他写法都会被当作不带锚点的 JavaScript 正则,因此 Edit.* 会同时匹配 Edit 和 NotebookEdit——只想指一个工具时,请用 ^Edit$ 加上锚点。matcher 区分大小写,而且每个事件匹配的是各自的字段:工具事件看工具名,SessionStart 看 source,SubagentStart 看智能体类型,Notification 看通知类型。6 若想对工具事件做更精细的过滤,处理器级别的 if 字段可以接受一条权限规则,例如 "Bash(git *)"——但它是尽力而为的(遇到无法解析的命令会放行),所以真正需要硬性保证时,请用权限规则而不是 if。6 还有一处语义变化值得知晓:自 v2.1.214 起,if 中的单段路径模式(例如 Edit(src/**))只匹配工作目录下顶层的 src;在该版本之前写下的 if 会悄悄不再匹配 packages/app/src/ 这类嵌套路径——想要过去那种任意深度的行为,请写成 Edit(**/src/**)。6
处理器共有五种类型:command(shell)、http(POST 端点)、mcp_tool、prompt(单轮模型评估)和 agent(一个拥有 Read/Grep/Glob 权限的子智能体,实验性)。默认超时:command/http/mcp_tool 为 600 秒(UserPromptSubmit 降为 30 秒,MessageDisplay 降为 10 秒),prompt 为 30 秒,agent 为 60 秒——可用 timeout 逐个覆盖。6 所有匹配上的钩子会并行运行,完全相同的处理器会被去重,而 $CLAUDE_PROJECT_DIR 让脚本指向你的项目根目录。
用 /hooks 来核验:这是一个只读浏览界面,会列出每个事件、其上配置的钩子,以及每条配置各自来自哪个设置文件。要改动就去编辑 JSON(或者让 Claude 代劳)。想临时全部关掉,设置 "disableAllHooks": true。6
五个模式
都做了通用化和最小化处理。钩子教程把其中几个做成了更完整的生产版本,面向 Apple 开发的钩子则把它们应用到 iOS 工具链上。
1. 编辑后自动格式化(PostToolUse)
直接取自官方指南——Claude 碰过的每个文件都会被格式化,没有例外:3
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{ "type": "command", "command": "jq -r '.tool_input.file_path' | xargs npx prettier --write" }
]
}
]
}
}
按你的技术栈把命令换成 ruff format、gofmt 或 swiftformat 即可。
2. 拦截危险命令(PreToolUse,exit 2)
#!/bin/bash
# .claude/hooks/guard-bash.sh — register on PreToolUse, matcher "Bash"
command=$(jq -r '.tool_input.command // empty')
case "$command" in
*"rm -rf"* | *"git push --force"* | *"DROP TABLE"*)
echo "Blocked: matches a destructive pattern. Propose a safer alternative." >&2
exit 2 ;;
esac
exit 0
exit 2 会拦下这次调用,并把 stderr 回传给 Claude,于是它会调整思路,而不是盲目重试。用 JSON 实现同样的效果就是带上原因的 permissionDecision: "deny",而且这条路还有向 "ask"(升级给人来定)或 updatedInput(改写命令)演进的余地。5
3. 会话开始时注入上下文(SessionStart)
SessionStart 钩子的纯文本 stdout 会直接成为 Claude 可见的上下文——不需要 JSON:1
#!/bin/bash
# .claude/hooks/session-context.sh — register on SessionStart
echo "Current branch: $(git branch --show-current)"
echo "Recent commits:"
git log --oneline -5
echo "Uncommitted files: $(git status --porcelain | wc -l | tr -d ' ')"
exit 0
这一招用于动态状态。静态约定应当写进 CLAUDE.md,文档本身也建议:不需要脚本的上下文,就交给 CLAUDE.md。1
4. 挂在 Stop 上的完成门禁
Stop 在 Claude 回答结束时触发。拦住它,就能逼着智能体一直干到条件满足为止:
#!/bin/bash
# .claude/hooks/stop-gate.sh — register on Stop
input=$(cat)
if [ "$(echo "$input" | jq -r '.stop_hook_active')" = "true" ]; then
exit 0 # already continuing because of this hook; don't loop forever
fi
if ! npm test --silent > /tmp/stop-gate.log 2>&1; then
jq -n '{decision: "block", reason: "Tests are failing. Fix them before finishing. Log: /tmp/stop-gate.log"}'
fi
exit 0
stop_hook_active 这项检查很关键:Claude Code 默认把 Stop 钩子的连续拦截限制在 8 次(可用 CLAUDE_CODE_STOP_HOOK_BLOCK_CAP 调高),而一个从不检查自己是否已经触发过续跑的门禁,会一口气把额度烧光。7 想要更柔和的引导,就返回 hookSpecificOutput.additionalContext 而不是 decision: "block"——同样让对话继续,但性质是带标签的反馈,而非钩子错误。至于一次性的条件,内置的 /goal 命令本身就是一个零配置、会话级、基于提示词的 Stop 钩子。1
5. 分发器:一个入口,许多小钩子
注册十个钩子,意味着十条会在不同机器和项目间逐渐走样的 settings.json 配置。另一种做法是:每个事件只注册一个分发器,然后按约定路由。
#!/bin/bash
# .claude/hooks/dispatch.sh — register once per event you care about
input=$(cat)
event=$(echo "$input" | jq -r '.hook_event_name')
dir="$CLAUDE_PROJECT_DIR/.claude/hooks/$event"
[ -d "$dir" ] || exit 0
for hook in "$dir"/*.sh; do
[ -x "$hook" ] || continue
echo "$input" | "$hook" || exit $?
done
exit 0
现在,加一个守卫只需要在 .claude/hooks/PreToolUse/ 里放一个新文件并 chmod +x——settings.json 岿然不动,每个脚本都小到可以单独测试,而且第一个 exit 2 会照常向上传递。有一个注意事项:分发器把 Claude Code 本会并行执行的东西串行化了,而且它最适合退出码型的钩子——会输出 JSON 的钩子应当单独注册,因为 stdout 里必须恰好只有一个 JSON 对象。5
钩子、CLAUDE.md、技能与记忆的分工
四种机制,四种职责:
| 机制 | 职责 | 选择标准 |
|---|---|---|
| 钩子 | 强制 | 如果“跳过”必须是不可能的(格式化、安全、门禁),那就用钩子 |
| CLAUDE.md | 引导 | 如果是模型每次会话都该知道的约定(技术栈、风格、命令),那就写进 CLAUDE.md |
| 技能 | 能力 | 如果是一套自带说明和脚本、在相关场景被调用的流程,那就是技能 |
| 记忆 | 回忆 | 如果是某次会话学到、往后会话还要用到的事实,那就是记忆 |
失败会朝两个方向发生。把约定写成钩子,换来的是一堆脆弱脚本,去强制那些一句话引导就能搞定的事。把策略写成 CLAUDE.md 里的散文,换来的是一个偏偏在关键那天向 main 强推的智能体。判断标准是:模型哪怕只忽略这一次,代价是什么?只是添堵,就归 CLAUDE.md;会变成事故,就归钩子。
钩子做不到的事
以下都是官方文档给出的诚实边界:7
- 钩子不能调用工具或斜杠命令。 命令型钩子只会说三种话:stdout、stderr 和退出码。经由
additionalContext返回的上下文会以纯文本注入。 PostToolUse无法撤销。 工具已经跑过了。防患于未然要靠PreToolUse。Stop在每次回答结束时都会触发,而不只是在“任务完成”时;用户手动中断时它不会触发(API 错误触发的是StopFailure)。门禁逻辑必须容得下任务中途的停止。PermissionRequest在纯无头(-p)运行中不会触发。 但当 Agent SDK 的canUseTool回调提供了提示时,它在-p下确实会触发,后台子智能体的工具调用也会触发;其余自动化场景请用PreToolUse。PreToolUse看不到用@引用的文件。 提示词里通过@拉进来的文件并不涉及工具调用;要挡住这条路径,请用Read的 deny 规则保护相应路径。1- 并行场景下的
updatedInput在设计上就不可靠。 当多个 PreToolUse 钩子改写同一个工具的参数时,只有一次改写能活下来,而且你无法决定是哪一次。让每次改写只归属一个钩子。 - 超时会取消钩子。 命令型钩子默认 600 秒(
UserPromptSubmit为 30 秒,MessageDisplay为 10 秒);一个会超时的慢门禁,等于一个没跑过的门禁。 - 输出上限为 10,000 个字符——超出的部分会写入文件,并以预览内容替代。
- 钩子以你的完整用户权限运行。 参考文档自己的警告是:它们“可以修改、删除或访问你的用户账户能访问的任何文件。在把钩子命令加入配置之前,请审阅并测试它们”。8 给变量加引号,用绝对路径,避开敏感文件。
- 一个坏掉的钩子会拖垮之后的每一次会话,直到你修好为止。可以用转录视图(
Ctrl+O)、claude --debug-file /tmp/claude.log,或会话中途的/debug来排查;一个经典的坑是:shell 配置文件在启动时打印了内容,把钩子的 JSON 输出弄坏了。7
常见问题
什么是 Claude Code 钩子?
钩子是用户自定义的命令——shell 脚本、HTTP 端点、MCP 工具或模型提示词——由 Claude Code 在特定生命周期节点自动执行。3 它们从 stdin 接收事件 JSON,并用退出码或 JSON 作答:拦截一次工具调用、注入上下文、改写参数、让智能体继续干活。与 CLAUDE.md 里的指令不同,它们每次都会执行,与模型的行为无关。
PreToolUse 钩子和权限有什么区别?
权限规则是声明式的:由 Claude Code 自己评估的静态 allow/deny/ask 模式。PreToolUse 钩子是可编程的:由你的代码检视完整的工具输入并作出判断。钩子在权限模式检查之前触发,因此钩子给出的 "deny" 即便在 bypassPermissions 模式下也依然有效——但钩子给出的 "allow" 无法推翻设置中的 deny 规则。4 凡是一条模式就能表达的,交给权限规则;当判断需要逻辑、外部状态或改写输入时,再动用钩子。
无头(-p)模式下钩子能用吗?
能用,只有一处细微差别:PermissionRequest 钩子会跳过纯 -p 运行(那里没有东西提供权限提示),不过当 Agent SDK 的 canUseTool 回调提供了提示时它会触发,后台子智能体的工具调用也会触发。纯无头运行中的自动权限判断,应当放进 PreToolUse。7 无头模式还解锁了一个交互式会话会忽略的选项:permissionDecision: "defer",它会暂停一次工具调用,好让外层进程(一个 Agent SDK 应用、一个自定义界面)收集输入,稍后再恢复会话。5
为什么我的钩子跑了却什么都没拦住?
几乎总是违反了契约。exit 1 不拦截——只有 exit 2 才拦,而且仅限支持拦截的事件。2 JSON 决策只在 exit 0 时被解析——一个先打印 {"decision": "block"} 再以 exit 2 结束的脚本,它的 JSON 会被丢弃。另外 matcher 区分大小写——bash 永远匹配不上 Bash。先用 /hooks 确认注册无误,再把示例 JSON 通过管道喂给脚本并检查 echo $?。7
参考来源
已对照 2026年8月8日的官方文档核实。钩子 API 在 Claude Code v2.1.x 的各次发布中发生过实质变化(新事件、新字段、matcher 语义),因此请把与版本相关的细节视为“截至该日期”的情况。
本站相关内容:想看包含提示词钩子与智能体钩子在内的完整系统视角,请读 Claude Code 指南的钩子章节;想要五个附完整配置的生产级实现,请读钩子教程;想看落到 iOS 上的应用模式,请读面向 Apple 开发的钩子;如果你还没装 Claude Code,就从快速上手开始。
-
Anthropic,“Hooks reference — Hook lifecycle and hook events”。code.claude.com/docs/en/hooks#hook-events ↩↩↩↩↩↩
-
Anthropic,“Hooks reference — Hook input and output; exit code output; exit code 2 behavior per event”。code.claude.com/docs/en/hooks#exit-code-output ↩↩↩↩↩↩↩↩
-
Anthropic,“Automate actions with hooks”。code.claude.com/docs/en/hooks-guide ↩↩↩
-
Anthropic,“Hooks guide — Hooks and permission modes”。code.claude.com/docs/en/hooks-guide#hooks-and-permission-modes ↩↩
-
Anthropic,“Hooks reference — JSON output and decision control”。code.claude.com/docs/en/hooks#json-output ↩↩↩↩↩↩↩
-
Anthropic,“Hooks reference — Configuration: hook locations, matcher patterns, hook handler fields, the /hooks menu”。code.claude.com/docs/en/hooks#configuration ↩↩↩↩↩↩↩
-
Anthropic,“Hooks guide — Limitations and troubleshooting”。code.claude.com/docs/en/hooks-guide#limitations-and-troubleshooting ↩↩↩↩↩
-
Anthropic,“Hooks reference — Security considerations”。code.claude.com/docs/en/hooks#security-considerations ↩
-
Anthropic,“Claude Code settings”。code.claude.com/docs/en/settings ↩