Hook 类型选型对比(详解 command、prompt 与 agent 的权限分层设计)

把规则”必然执行”与”主观判断”分开,是 Claude Code 权限架构里一处值得拆开看的分层。command-type hook 用 exit code 表达 allow / deny,跑在毫秒级、结果可复现;prompt-type hook 把事件喂回 LLM 拿一次 yes / no;agent-type hook 拉起带工具的子 agent 做多轮验证。三者的区别不在”强不强”,而在”在哪一层做决策”。

一、四类 hook 各自在权限架构里的位置

类型 决策机制 延迟级别 是否确定 适合场景
command exit code 0/2 + JSON 毫秒 是 路径校验、命令黑名单、格式化
http HTTP 状态码 + JSON 数十到数百毫秒 取决于接口 Slack / 监控上报、远程审计
prompt LLM 单次调用返回 ok/reason 数百到数千毫秒 否 复杂语义判断、代码评审
agent 子 agent + 工具多轮 数秒到数十秒 否 跨文件验证、深度审查

从架构视角看,command / http 是 harness 自己执行的”硬逻辑”;prompt / agent 是把决策交还给 LLM 的”软逻辑”。这两层并不是互斥关系,而是同一权限管线上由快到慢、由确定到主观的递进。

二、为什么 command 适合 deterministic rules

deterministic rules 的特征是输入完全决定输出:给一个 bash 命令字符串,匹配 rm -rf / 就该 block;给一个文件路径,越界就该 deny。这类规则用 exit code 表达比让 LLM 判断便宜很多。

# .claude/hooks/block-force-push.sh
#!/usr/bin/env bash
input=$(cat)
cmd=$(printf '%s' "$input" | jq -r '.tool_input.command // empty')
if printf '%s' "$cmd" | grep -qE 'git\s+push\s+--force\s+.*\bmain\b'; then
  echo "force push to main is blocked" >&2
  exit 2   # 退出码 2 表示「明确阻断」并把 stderr 反馈给模型
fi
exit 0

退出码的语义必须区分清楚:0 表示通过;2 表示「我有意阻断」,stderr 内容会作为反馈回到主上下文;其它非 0 值会被 harness 静默记入 verbose 日志,模型看不到,这是一类常被忽略的”hook 没生效”原因。

注册到 PreToolUse 时,多个 hook 按定义顺序串行执行,遇到 deny 直接终止。一个常见的顺序原则是按延迟从低到高排开,让最便宜的判断先过滤掉绝大多数请求,剩下再交给昂贵的判断处理。

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          { "type": "command", "command": "bash .claude/hooks/block-force-push.sh" },
          { "type": "command", "command": "bash .claude/hooks/validate-paths.sh" },
          { "type": "command", "command": "bash .claude/hooks/log-action.sh" }
        ]
      }
    ]
  }
}

把黑名单、路径白名单、操作审计三个 command hook 串联,对每次 Bash 工具调用产生一致的硬约束。CLAUDE.md 里的”不要 force push”是建议,能被遗忘;这套 PreToolUse 链是必然执行。

三、为什么 prompt 与 agent 适用于 judgment

judgment 类问题的输入与输出不是函数式关系,需要看上下文、风格、安全边界。这种事让 LLM 来做比写一堆正则更稳。

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Write",
        "hooks": [
          {
            "type": "prompt",
            "prompt": "Review the file write. Approve only if the change matches project conventions and does not introduce secrets."
          }
        ]
      }
    ]
  }
}

prompt hook 调一次 LLM(默认 Haiku),把事件 JSON 当模板变量喂进去,等一个 {"ok": true/false, "reason": "..."} 回来。它仍然是同步阻塞主流程,但已经能理解”这段 diff 是不是符合团队命名约定””有没有把 token 写进文件”。

agent hook 更进一步:它会拉起一个带工具访问的子 agent,可以读文件、grep、跑测试。代价是数秒到数十秒延迟,能耗显著上升。典型场景是”提交前整库扫描”或”PR diff 走完整 lint + 单元测试”。

决策层级 执行者 上下文消耗 速度 一致性
command harness 极低 极快 100%
http 外部服务 低 快 取决于接口
prompt LLM 单次 中 中 不一致
agent LLM 多轮 + 工具 高 慢 不一致

四类的速度与智能呈反向关系。选错层级的代价很直接:command 套到模糊规则上会漏放;prompt/agent 套到硬黑名单上既慢又不可靠。这种分层设计本质上是”把能用代码算的算清楚,把必须判断的交给模型”。

四、permission 分层在配置层级的体现

hook 不是独立存在的,它和 settings 优先级、工作区信任、permission mode 共同构成完整权限栈。三类决策层在配置上各有落点:

  1. command hook 放在 .claude/settings.json(项目)或 ~/.claude/settings.json(用户),作为团队共享的硬约束;
  2. prompt hook 常放在 settings.local.json(本地覆盖),避免给全团队引入额外延迟;
  3. agent hook 通常绑定在 SubagentStart / SubagentStop 等 agent 类事件上,作为研究型子 agent 的安全护栏。
{
  "hooks": {
    "SubagentStart": [
      {
        "matcher": "Explore",
        "hooks": [
          {
            "type": "agent",
            "prompt": "Verify the read-only research task does not request write tools; if it does, deny."
          }
        ]
      }
    ]
  }
}

权限栈的优先级从下到上是:managed policy → 项目 settings → 用户 settings → local settings。hook 决策结果和 settings 中的 permissions.allow / deny 列表共同决定一个工具调用最终是被放行、拒绝还是询问用户。deny > defer > ask > allow 的判定顺序意味着:哪怕一个 hook 想放行,另一个 hook 拒绝也会让调用直接失败。

五、落地的两个常见错误

把高延迟 hook 串在链首会让每次工具调用都很慢。先用 exit code 0 的 command 把绝大多数情况过滤掉,再让 prompt/agent 处理剩下的灰色地带,是更省钱的组合。

另外,hook 的 stdio 协议一旦写错就被 harness 静默忽略。例如 prompt hook 期望返回 JSON 但脚本输出了纯文本,或 json 字段拼写错把 ok 写成 approved,都会被当成”hook 失败”且不阻断。排查时打开 ~/.claude/debug/latest 日志、确认 hook 在 verbose 模式下确实被调用,是最快定位路径。

到这里,把规则必然执行与主观判断分层落地的链路就完整了:command 兜底硬约束,http 走外部联动,prompt 处理中等复杂度的判断,agent 处理需要工具访问的深度审查。

常见问题(FAQ)

Q1:command hook 退出码 1 和 2 到底差在哪?

退 2 是有意阻断,stderr 回到主上下文;退 1 是脚本异常,harness 静默记录,模型看不到。

Q2:prompt hook 的额外 token 成本大概是什么量级?

每次 hook 调用是一次 LLM 调用,输入为事件 JSON,输出只有 ok / reason,对主上下文窗口几乎无压力。

Q3:agent hook 跑超时怎么办?

默认 60 秒,可在配置里调 timeout;超时本身被当作 hook 失败,不会阻断原始操作。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 qiqicto@qq.com 举报,一经查实,本站将立刻删除。
赞 (0)
其AI的头像其AI普通用户

相关推荐

返回顶部