跳到主要内容

Claude Code Hooks如何守住自动化边界

有些要求写进提示词就够了,例如“解释尽量带代码位置”。有些动作漏一次就可能出问题,例如改完 OpenAPI 没跑兼容检查、准备执行生产部署命令、没有验证证据就结束任务。

Claude Code Hooks 把这类动作挂到固定生命周期节点。事件发生并命中条件后,Claude Code 自动运行指定脚本、HTTP 请求、MCP 工具或模型判断。执行时机由运行时控制,不依赖模型在长会话里还记不记得。

一个Hook是怎样命中的

以“禁止直接执行生产 Kubernetes 变更”为例,完整判定经过四步:

  1. Claude 准备调用 Bash;
  2. PreToolUse 事件触发;
  3. matcher 确认工具名是 Bash;
  4. handler 读取 stdin 中的命令,发现生产操作后拒绝。
模型提出工具调用

生命周期事件触发

matcher筛选事件

handler读取事件JSON

返回结果或退出码

Claude Code合并决策并继续、询问或阻断

Hook事件判定链路图

Hooks和自然语言规则怎样分工

需求更适合的机制原因
说明项目架构和编码习惯CLAUDE.md需要模型理解并灵活应用
一套接口审查步骤Skill特定任务按需加载
修改契约文件后运行检查PostToolUse Hook时机固定,动作明确
工具执行前拦生产写操作PreToolUse Hook或权限规则需要在副作用发生前处理
主 Agent 结束前检查证据Stop Hook每轮停止前触发
阻止读取密钥目录permissions.deny客户端强制限制更直接

Hooks 很适合自动化卡点,仍不能替代权限和隔离。安全要求能用 deny 规则表达时,优先让权限系统执行;Hook 更适合需要读取事件参数、运行自定义判断的场景。

配置放在哪里

常见设置位置有三类:

文件范围典型内容
~/.claude/settings.json当前用户所有项目个人通知、通用审计
.claude/settings.json当前项目,可提交Git团队共享的校验和安全规则
.claude/settings.local.json当前项目,仅本机本地服务地址、个人工具路径

插件、托管设置、Skill 和 Subagent Front Matter 也能提供 Hooks。不同来源的 Hook 会合并,同一事件下的匹配 handler 可能同时运行。

项目级 Hook 来自仓库内容,首次使用要关注工作区信任。脚本拥有当前进程可以访问的文件和环境,Review 强度应该和构建脚本、CI 脚本一样。

一个最小配置有三层

{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/check-api-contract.sh",
"args": []
}
]
}
]
}
}
  • PostToolUse 是生命周期事件;
  • matcher 只选择 Edit 和 Write;
  • 内层 hooks 是执行动作的 handler。

设置了 args 后,Claude Code 使用 exec form 直接启动命令,不让 Shell 再拆分路径。脚本路径含空格或特殊字符时更稳。需要管道、重定向和 && 等 Shell 能力时,才使用不带 args 的 shell form。

matcher怎样写才不会误伤

当前官方规则可以概括成三类:

  • "*"、空字符串或省略:匹配该事件的所有发生;
  • 只含普通名称字符时:按精确名称或 |, 分隔列表匹配;
  • 包含正则特殊字符时:作为 JavaScript 正则表达式匹配。

工具事件通常按 tool_name 匹配:

"matcher": "Bash"
"matcher": "Edit|Write"
"matcher": "mcp__observability__.*"

matcher 要尽量收窄。每次工具调用都触发一个耗时脚本,会让 Agent Loop 明显变慢。

某些事件不支持 matcher,例如 StopTaskCreatedTaskCompleted 会在每次对应事件上触发。配置前要查当前 Hook Reference,不能默认所有事件都能用同一套筛选方式。

五类Handler怎么选

类型工作方式适合场景
command执行本机命令或脚本校验、格式化、日志、安全判断
http把事件JSON POST到服务端集中审计、远程策略、通知服务
mcp_tool调用已连接MCP Server的工具复用现有平台能力
prompt让模型做一次单轮判断输入已经足够的语义检查
agent启动可读文件和搜索的Subagent判断需要多步验证的质量门禁

规则能够写成确定脚本时,command 最容易测试和审计。promptagent 会使用模型,存在额外 Token、延迟和判断波动;agent Handler 当前仍属于实验能力。

HTTP Hook 的非 2xx、连接失败和超时属于非阻断错误,流程会继续。需要拒绝工具调用时,服务端应返回 2xx,并在 JSON Body 中给出合法的 deny 决策。

Hook拿到什么输入

Command Hook 从 stdin 读取 JSON,HTTP Hook 从 POST Body 读取同一类数据。常见公共字段包括:

字段含义
session_id当前会话标识
transcript_path会话记录路径,写入可能略有延迟
cwdHook触发时的工作目录
permission_mode当前权限模式
hook_event_name事件名称
tool_name工具事件中的工具名
tool_input工具参数

准备执行模块测试时,PreToolUse 可能收到:

{
"session_id": "session-123",
"cwd": "/workspace/mall-service",
"permission_mode": "default",
"hook_event_name": "PreToolUse",
"tool_name": "Bash",
"tool_input": {
"command": "./mvnw -pl billing-service test"
}
}

脚本应该用 JSON 解析器读取字段,别用字符串切割硬猜格式:

#!/usr/bin/env bash
set -euo pipefail

input="$(cat)"
tool_name="$(jq -r '.tool_name // empty' <<<"$input")"
command="$(jq -r '.tool_input.command // empty' <<<"$input")"

退出码和stdout怎样影响流程

退出码会直接决定 Hook 报告成功、阻断流程还是只记录一条非阻断错误。

exit 0

表示 Hook 自身执行成功。Claude Code 会尝试解析 stdout 中的结构化 JSON。需要返回 JSON 时,stdout 只能放一个 JSON 对象;调试信息写到日志文件。

exit 2

表示阻断错误。Claude Code 忽略 stdout,把 stderr 作为错误原因。具体效果取决于事件:

  • PreToolUse:阻止工具执行;
  • PermissionRequest:拒绝权限;
  • UserPromptSubmit:拒绝本次Prompt处理;
  • Stop:阻止停止,让会话继续;
  • PreCompact:阻止压缩;
  • PostToolUse:工具已经成功执行,只能把反馈交给 Claude,无法撤销副作用。

其他非零退出码

对大多数事件属于非阻断错误,流程继续。Unix 脚本常用的 exit 1 在这里通常拦不住工具调用。策略型 Hook 要明确使用 exit 2exit 0 加合法 JSON。

Hook输入输出与退出码判定图

常用生命周期事件怎样选

事件触发点常见用途
SessionStart会话启动或恢复注入环境、加载动态上下文
UserPromptSubmit用户消息交给Claude之前审计Prompt、补上下文
PreToolUse工具执行之前风险拦截、参数检查
PermissionRequest需要权限判断时审计、窄范围批准或拒绝
PostToolUse工具成功以后校验、格式化、记录
PostToolUseFailure工具失败以后错误归档、补充诊断
PostToolBatch一批并行工具调用结束后聚合检查、阻止下一轮模型调用
NotificationClaude Code发出通知时桌面提醒、消息推送
StopClaude准备结束本轮时完成条件与证据门禁
PreCompact / PostCompact压缩前后备份状态、审计压缩
SubagentStart / SubagentStop子代理开始和结束注入角色上下文、验证结果
TaskCreated / TaskCompleted任务创建和完成任务命名、完成证据检查

PreToolUsePostToolUsePermissionRequest 经常混淆。判断方法很简单:是否需要在执行前拦截,是否只做执行后收尾,还是权限弹窗出现时才介入。

Claude Code Hooks生命周期地图

示例一:修改OpenAPI后自动跑兼容检查

这个 Hook 发生在文件修改成功以后。它只检查契约文件,普通 Java 改动不会触发实际校验。

.claude/hooks/check-api-contract.sh

#!/usr/bin/env bash
set -euo pipefail

input="$(cat)"
file="$(jq -r '.tool_input.file_path // empty' <<<"$input")"

case "$file" in
*/openapi.yaml|*/openapi.yml)
if ! ./scripts/check-openapi-compat.sh "$file"; then
echo "OpenAPI兼容检查失败,请先处理报告中的破坏性变更。" >&2
exit 2
fi
;;
esac

exit 0

配置:

{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/check-api-contract.sh",
"args": []
}
]
}
]
}
}

脚本失败时使用 exit 2,Claude 能看到 stderr 并继续修正。文件修改已经发生,Hook 不能回滚。需要禁止契约修改的场景应该在 PreToolUse 或权限层处理。

示例二:拦截生产Kubernetes命令

.claude/hooks/block-production-command.sh

#!/usr/bin/env bash
set -euo pipefail

input="$(cat)"
tool_name="$(jq -r '.tool_name // empty' <<<"$input")"
command="$(jq -r '.tool_input.command // empty' <<<"$input")"

if [[ "$tool_name" != "Bash" ]]; then
exit 0
fi

if [[ "$command" == *"--context=prod"* ]] || \
[[ "$command" == *"--namespace=production"* ]] || \
{ [[ "$command" == *"helm upgrade"* ]] && [[ "$command" == *"production"* ]]; }; then
echo "已阻止生产集群变更。请走发布平台并等待人工审批。" >&2
exit 2
fi

exit 0

配置到 PreToolUse

{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/block-production-command.sh",
"args": []
}
]
}
]
}
}

示例展示的是执行前拦截。真实生产安全不能只依赖关键词黑名单,还要配合独立凭据、网络边界、集群RBAC、发布审批与审计。

示例三:结束前检查是否有验证证据

Stop Hook 可以让模型检查本轮是否提供了测试或验证结果。语义判断较复杂时,可以使用 Prompt Handler:

{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "prompt",
"prompt": "检查本轮是否修改了代码。如果修改过,是否给出了实际验证命令及结果;若缺少证据,返回阻止停止的决定,并明确还要补什么。事件输入:$ARGUMENTS"
}
]
}
]
}
}

Stop 只是“本轮准备结束”,并不自动等于完整任务已经完成。质量门禁还要防止循环:如果 Hook 一直要求继续,Claude 可能反复补充却无法满足模糊条件。Prompt 要写出可检查的完成标准,并设置合理超时。

多个Hook命中时不要依赖执行顺序

匹配到的 Hooks 会并行运行。一个安全 Hook 拒绝工具调用,旁边的日志 Hook 仍可能执行;多个 Hook 同时修改工具输入,最后结果还可能与完成顺序有关。

因此:

  • 会写文件、发请求的 Hook 自己判断是否该执行;
  • 不假设另一个 Hook 会先拒绝;
  • 不让多个 Hook 修改同一个输入字段;
  • Handler 尽量保持幂等;
  • 日志带上 session、事件和工具标识,方便排查。

怎样调试一个没有生效的Hook

  1. 运行 /hooks,确认事件、matcher、handler和来源;
  2. 检查 JSON 文件位置和语法;
  3. 用保存的事件 JSON 手工执行脚本;
  4. 检查脚本是否可执行,依赖是否在 PATH
  5. 把调试输出写日志,避免污染 stdout JSON;
  6. 确认事件是否真的发生,例如非交互模式未必出现权限弹窗;
  7. 检查返回码,策略阻断不要误用 exit 1
  8. HTTP Hook 需要返回 2xx 加合法决策,单独返回 403 只会被当成非阻断错误。

ClaudeCode的Context命令

面试回答怎样落到工程边界

面试回答可以这样组织

Claude Code Hooks是在生命周期节点自动执行的扩展机制。事件触发后先经过matcher,再把JSON输入交给command、http、mcp_tool、prompt或agent handler。PreToolUse适合副作用前拦截,PostToolUse适合执行后校验,Stop适合完成证据门禁。Command Hook通常用exit 0配合结构化stdout,或用exit 2加stderr阻断;exit 1对大多数事件只算非阻断错误。多个匹配Hook会并行运行,所以脚本要幂等,不能依赖执行顺序。高风险边界仍要结合权限、Sandbox、基础设施权限和CI。

参考资料

🎁优惠