Hook 即闸门:把 agent 行为焊死在流程里
用 coding agent 一段时间后你会撞上同一个问题:CLAUDE.md 写得再细,模型上下文一长就可能忘;提示词里的”请勿”再诚恳,也只是概率。规则想要真的生效,不能靠模型自觉,得靠每次必然执行的代码。Claude Code 的 hooks 就是干这个的——官方定义很直白:hooks 是用户定义的 shell 命令、HTTP 端点或 LLM 提示,在生命周期中的特定点自动执行。[1] 它是 shell 命令,不是又一段提示词;触发即运行,不经过模型的记忆。
hooks 挂在三个粒度上:每个会话一次的 SessionStart/SessionEnd,每轮一次的 UserPromptSubmit/Stop,以及代理循环里每个工具调用的 PreToolUse/PostToolUse。[1] 日常最有价值的集中在工具调用这一层:工具执行前拦截,工具成功后收尾。[5]
协议比配置更值得先看#
hook 脚本的输入输出协议很朴素:Claude Code 把会话 id、工作目录、工具名和工具参数打包成 JSON,从 stdin 传给脚本;[1] 脚本用 jq 取字段,按退出码表态。一次 Bash 调用的输入长这样:{"session_id":"abc123","cwd":"/Users/me/myproject","hook_event_name":"PreToolUse","tool_name":"Bash","tool_input":{"command":"npm test"}}。[2] 脚本只读 stdin、只写 stdout/stderr,不依赖任何魔法环境变量,所以每段 hook 逻辑都能脱离 Claude Code 单独测试——这是它比提示词可靠的根本原因。
退出码只有两种含义:exit 0 表示没有决策,工具调用继续走正常权限流程;exit 2 表示阻止操作,脚本写到 stderr 的内容会变成 Claude 的反馈。[3] 官方强调过一个容易踩的细节:hook 可以拒绝调用,但保持沉默不会批准它。[1] 不返回 deny 不等于放行;想让拒绝带理由,就输出结构化 JSON,把 permissionDecision 设为 "deny" 并附上原因,Claude 会看到并换一条路走。[1] 更精细的还有 "ask"(转人工)和 "defer"(推迟决定),配合 requiresUserInteraction 可以拼出”危险操作一律先问人”的策略。[2]
筛选也分两层。matcher 按工具名过滤,比如只对 Edit|Write 生效;要按参数过滤,用 if 字段,它走权限规则语法,"Bash(git *)" 只匹配 Bash 子命令含 git 的调用,"Edit(*.ts)" 只对 TypeScript 文件生效。[1] 一个实际的例子:把 PreToolUse 的 matcher 设为 Bash,再在 if 里写 "Bash(rm *)",block-rm.sh 就只在命令匹配 rm * 时被拉起,npm test 连进程都不会多开一个。[1]
别只写 shell,还有三种更重的类型#
除了 command 类型,官方还提供了 http、mcp_tool、prompt 和 agent 四种 handler。[1] prompt 型 hook 让一个小模型按你的指示评估事件并返回 {"ok": false, "reason": "..."},典型用法是挂在 Stop 上检查任务是否真的做完。[3] agent 型更进一步,直接派一个完整 agent 跑测试验证再回报。[3] 这两类把”确定性闸门”和”智能判断”接了起来,适合做交付验收这类需要看上下文的检查。http 型把事件 POST 给内部服务,适合复用已有的合规或审计接口;mcp_tool 型直接调用已连接的 MCP 工具,比如把每次文件写入喂给安全扫描器。[2] 脚本型 hook 讲究轻快,别把全量测试挂到每次编辑后,那是 CI 的活。
CLAUDE.md 定方向,hook 守底线#
CLAUDE.md 和 hook 是互补的,不是二选一。前者告诉模型”项目长什么样、怎么构建”,后者规定”每个动作必须经过什么检查”。官方指南的定位很准确:当 Claude Code 编辑文件、完成任务或需要输入时自动运行 shell 命令——格式化代码、发送通知、验证命令并强制执行项目规则。[3] 凡是”每次都必须做”的事,比如改完必格式化、敏感文件必拦截,就该从文档搬进 hook;凡是”看情况”的事,留在 CLAUDE.md 里交给模型判断。分界线只有一条:你愿不愿意为它承担一次失误的成本。
快速上手:10 分钟装两道闸#
按官方指南的顺序来,从最稳的两道闸开始。[3]
mkdir -p .claude/hooks
第一道:保护文件。PreToolUse 拦截对 .env、锁文件等敏感路径的写入:
# .claude/hooks/protect-files.sh
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
第二道:编辑后自动格式化,PostToolUse 每次改完立刻跑 prettier:[3]
{
"hooks": {
"PreToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{ "type": "command", "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/protect-files.sh" }
]
}
],
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{ "type": "command", "command": "jq -r '.tool_input.file_path' | xargs npx prettier --write" }
]
}
]
}
}
chmod +x 后先本地验证再进会话:[3]
chmod +x .claude/hooks/protect-files.sh
echo '{"tool_input":{"file_path":"config/.env"}}' | .claude/hooks/protect-files.sh; echo $?
预期输出 Blocked... 且退出码为 2。进会话后跑 /hooks 确认配置被加载,[3] 钩子没触发时用 claude --debug-file /tmp/claude.log 看执行记录。[3] 忘了 chmod +x 是 hook 静默失效的头号原因,其次是脚本里用了不在 PATH 上的 jq,这两点都靠本地那一步验证兜底。[5]
把闸门当团队资产#
项目级的 .claude/settings.json 可以提交进仓库,规则随代码走,新同事 clone 下来就有同样的闸门;.claude/settings.local.json 则按设计被 gitignore,适合放只有你本地生效的实验规则。[2] 官方还支持把 hooks 写进 skill 或 subagent 的 frontmatter,技能被调用时注册、subagent 运行期间生效,规则跟着组件走,比全局铺开更克制。[2] 排障时 /hooks 是只读浏览器,能看到每个事件的配置数、来源文件和完整命令。[2] 想临时关掉全部钩子,"disableAllHooks": true 一行即可,不用删配置。[2]
进阶玩法是把 pre_tool_use 的 Python 脚本按事件拆开管理,甚至让 prompt 型 hook 自己校验输出。[4] 我的建议:先装”记录”,再装”格式化”,最后装”阻止”。每加一道闸都先跑一周观察误伤,deny 消息必须写明替代路径,否则 agent 被挡在原地,比没有 hook 更糟。规则不是越多越安全,是能被审计的才安全。
Sources#
[1] https://docs.anthropic.com/zh-CN/docs/claude-code/hooks — Hooks 参考(Claude Code 官方文档·中文) [2] https://docs.anthropic.com/en/docs/claude-code/hooks — Hooks reference(官方文档·英文) [3] https://code.claude.com/docs/zh-CN/hooks-guide — 使用 hooks 自动化操作(官方指南·中文) [4] https://github.com/disler/claude-code-hooks-mastery — claude-code-hooks-mastery(GitHub 实战仓库) [5] https://claudelab.net/en/articles/claude-code/claude-code-hooks-showcase-guide — Claude Code Hooks 实战指南(claudelab)