找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

3499

积分

0

好友

516

主题
发表于 2 小时前 | 查看: 3| 回复: 0

一、安全门禁:PreToolUse + Command Hook

📍 安全门禁:PreToolUse + Command Hook
在 Bash 命令执行前拦截,用 if/else 规则判断,不需要 LLM 介入,效率最高。

做安全门禁,第一反应可能是“我让 AI 来判断这个命令危不危险”——用 Prompt Hook 或 Agent Hook,让 LLM 读命令然后说“允许/拒绝”。 但这个思路是错的。

安全门禁的判断逻辑 不需要语义理解rm -rf /tmp/*rm -rf /node_modules 的区别,任何人,不需要懂英语,都能判断出来——前者删的是系统临时目录,后者删的是依赖。这类规则的判断本质上是结构化的 if/else,不需要 LLM。 所以用 Command Hook 而不是 Prompt/Agent Hook,效率最高,延迟最低。

这里有个核心原则:门禁规则要少。20 条规则等于没有门禁。Claude 会无视它们,你也迟早会无视它们。真正有效的安全门禁,就那么三四条——只拦截那些真正会造成损害的操作。

什么算“真正会造成损害”?

  • 删除操作且目标是系统关键路径
  • 网络请求发到未经授权的外部地址
  • 修改系统配置文件
  • 涉及生产数据库的写操作

什么 不算 危险操作?

  • rm -rf node_modules (依赖可以重装)
  • rm -rf dist (构建产物可以重跑)
  • 常规的 git add .git commit

很多人把 Hooks 当成“代码审查工具”来用,想拦截所有“代码写得不好”的操作。这是把 Hooks 和 CI 搞混了——我们在第四节会专门讲这个边界。

Anthropic 官方提供了一个参考实现(bash_command_validator_example.py),核心逻辑可以直接用:

#!/usr/bin/env python3
import sys, json

data = json.load(sys.stdin)
command = data.get("tool_input", {}).get("command", "")

dangerous_patterns = [
    "rm -rf /",
    "rm -rf /tmp",
    ":(){:|:&};:",  # fork bomb
    "> /dev/sda",
]

for pattern in dangerous_patterns:
    if pattern in command:
        print(json.dumps({
            "hookSpecificOutput": {
                "hookEventName": "PreToolUse",
                "permissionDecision": "deny",
                "permissionDecisionReason": f"Dangerous pattern detected: {pattern}"
            }
        }))
        sys.exit(2)  # exit code 2 = block

sys.exit(0)  # allow

Exit Code 2 是关键。在 PreToolUse 事件里返回 2,Claude 会阻止这个工具调用,这条 Bash 命令不会执行。

有时候一条命令是否危险,取决于当前在哪个环境里。在生产环境里跑 npm publish 是危险的,在开发环境里是正常的。 Command Hook 可以读取环境变量做上下文判断:

#!/bin/bash
ENVIRONMENT=${CLAUDE_PROJECT_ENV:-development}
COMMAND=$(jq -r '.tool_input.command')

if [[ "$COMMAND" == *"npm publish"* ]] && [[ "$ENVIRONMENT" == "production" ]]; then
  jq -n '{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"deny","permissionDecisionReason":"Publishing directly to production is not allowed"}}'
  exit 2
fi
exit 0

CLAUDE_PROJECT_ENV 这个变量需要在 Claude Code 启动前通过环境变量注入,或者写在 .env 文件里让 Hook 读取。

二、异步通知:PostToolUse + Async Hook

上篇我们说过,async: true 让 Hook 在后台运行,Claude 不等它,继续工作。 这个特性最适合什么场景? 不阻塞 Claude 工作、但需要在事后知道结果的操作。 最典型的例子:测试。Claude 写完代码,你想自动跑测试验证它有没有写对,但你不想等测试跑完才让 Claude 继续——Claude 完全可以继续下一个任务,测试结果等它跑完了再告诉 Claude。

这里有一个关键细节在上篇没展开讲:async Hook 怎么把结果传给 Claude? 答案是:只能通过 stdout,而且只有 systemMessage 字段会被 Claude 看到。

#!/bin/bash
INPUT=$(cat)
FILE=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')

# 只对源代码文件跑测试
[[ "$FILE" != *.ts && "$FILE" != *.js ]] && exit 0

RESULT=$(npm test 2>&1)
EXIT=$?

if [ $EXIT -eq 0 ]; then
  echo "{\"systemMessage\": \"✅ Tests passed after editing $FILE\"}"
else
  echo "{\"systemMessage\": \"❌ Tests failed after editing $FILE: $RESULT\"}"
fi
exit 0  # async hook 里 exit code 不影响 Claude,Claude 不等它

注意这里的 exit 0——async Hook 的 exit code 对 Claude 没有任何影响,Claude 根本不等它。真正把结果传给 Claude 的只有 stdout 里的 systemMessage

systemMessage 的本质是下一次对话时才反馈,不是实时的。 如果 Claude 在 async Hook 执行期间没有新的输入,systemMessage 就会“丢失”——因为 Claude 没有被重新唤醒,不会有机会读取这条消息。 所以 async Hook 不是“后台监控”,而是“异步通知”。它的设计假设是:Claude 在 Hook 运行期间会继续工作、提交新的 prompt,这时候上一轮的结果才会作为上下文回到 Claude 的视野里。

三、质量门禁:Stop Hook + Agent Hook

📍 质量门禁:Stop Hook + Agent Hook
Claude 说完成了之前,强制它通过子 Agent 验证测试是否通过、TODO 是否清理。

这是我认为最有价值的一个场景,也是最容易忽略的一个。 Claude Code 是一种自主代理——它自己决定什么时候停下来。你提交一个任务,它执行一系列操作,然后说“已完成”。 问题是:它说“完成了”的时候,可能并没有真的完成。 常见的情况:

  • 测试没跑,它以为代码写对了
  • 有 TODO 遗留,它觉得差不多了
  • 某个相关文件没改,它觉得改完了
  • git 没 commit,它不知道要不要 commit

传统的 CI 能检查测试是否通过、代码是否符合规范——但 CI 在代码写完之后才跑,而且 CI 跑的时候 Claude 已经结束会话了,同样的错误下次还会犯。 Stop Hook 解决的是这个问题:在 Claude 想停下来之前,强制它做一次质量确认。

上篇我们讲过 Handler 的选择逻辑。Stop Hook 的场景是:

  • 判断需要执行命令(跑测试、检查 TODO、检查 git 状态)
  • 判断需要上下文(这次改了什么文件、和上次任务有什么关系)

这类判断无法用 Command Hook 完成,因为 Command Hook 只做 if/else 规则判断,不能调用工具。 而 Prompt Hook 只能做语义判断,不能查证——它没法真的去跑 npm test,只能猜“这段代码可能有问题”。 所以 Stop Hook 只能用 Agent Hook。Agent Hook 会 Spawn 一个子 Agent,给它工具权限,让它自己去做查证。

Stop Hook 里有一个细节特别容易混淆:exit code 2 到底是什么意思?

Exit Code 0 + JSON {"continue": false, "stopReason": "..."}  → Claude 停止
Exit Code 2                                     → Claude 继续工作(不停止)

Exit Code 2 在 PreToolUse 里是“阻止操作”,在 Stop 里是“让 Claude 继续”。 这是一个反直觉的设计——原因是 Stop Hook 的本意不是“拦截停止”,而是“在停止之前做一次检查”。如果检查通过,返回 exit code 0 + {"continue": false},Claude 停止。如果检查没通过,返回 exit code 2,Claude 继续工作,它会根据你给的反馈重新审视任务。 所以 Stop Hook 的 Agent 返回格式应该是:

{
  "continue": false,
  "stopReason": "All checks passed",
  "feedback": "Tests are green, no TODO comments found, all changes committed."
}

或者检查没通过:

{
  "continue": false,
  "stopReason": "Checks failed",
  "feedback": "2 unit tests are failing. Please run npm test and fix them before stopping."
}

Claude 看到这类 JSON 反馈,会理解为什么被阻止,或者为什么可以通过。详细的配置可以通过相关的 技术文档 进行查阅。

{
  "matcher": "",
  "hooks": [
    {
      "type": "agent",
      "prompt": "Verify the following conditions before allowing Claude Code to stop:\n1. All unit tests pass (run npm test or the project's test command)\n2. No TODO or FIXME comments left in the code\n3. All changes are committed to git (run git status to check)\n\nProject directory: $CLAUDE_PROJECT_DIR\n\nRespond with JSON in this format:\n- If all checks pass: {\"continue\": false, \"stopReason\": \"All checks passed\", \"feedback\": \"...\"}\n- If any check fails: {\"continue\": false, \"stopReason\": \"Checks failed\", \"feedback\": \"Describe what failed and what needs to be done\"}",
      "timeout": 120
    }
  ]
}

matcher: "" 表示匹配所有情况——不管什么事件触发,Stop Hook 都要运行。

四、Hooks 与 CI 的关系:边界在哪里?

这是很多人最容易搞混的地方。 Hooks 在 AI 执行流程内部,CI 在外部。 外部检查的问题是:AI 已经执行完了,你才检查,检查结果出来了,AI 已经走了。下次同样的问题,AI 还会再犯,因为 CI 反馈没有进入它的长期上下文。 内部检查的优势是:Claude 能在同一个 session 里感知到“这次命令被拦了”,下一次它会调整。

但这不代表 Hooks 能替代 CI。它们有各自的边界:
Hooks 适合做的事(内部干预):

  • 在危险操作执行前拦截
  • 在 Claude 想停止前做质量确认
  • 依赖 AI 行为上下文的判断(比如“这次修改和上次任务是否相关”)

CI 适合做的事(外部检查):

  • 代码覆盖率是否达标
  • 编译是否通过
  • 安全扫描(漏洞检测、依赖审计)
  • 风格检查(lint、format)

两者都适合做的事:

  • 测试——Hooks 里可以触发,CI 里也要有最终断言

一个协作流程的例子:Claude 写代码 → PostToolUse Hook 异步跑测试 → 测试失败 systemMessage 告诉 Claude → Claude 修复 → Claude 说“完成了” → Stop Hook 验证测试通过 → 提交 PR → CI 最终跑完整测试套件 → 通过才合入。

五、总结与避坑

1. Hooks 是给 AI 立法,不是给 AI 打工
你不应该用 Hooks 去替 Claude 做判断,而应该去定义边界、划定红线。Claude 有自己的推理能力,Hook 的作用是设立禁区,而不是全程押送。门禁越少越有效——规则少,Claude 才真的会遵守。

2. 门禁少而精准,多了等于没门禁
一个项目配 50 条 Hooks 规则,Claude 会无视它们,你也会无视它们。真正有效的 Hooks 配置就那么三四条。

3. 最强的 Hook 是让 Claude 自己判断
Prompt Hook 和 Agent Hook 之所以比 Command Hook 更有价值,是因为它们不是在做模式匹配,而是在做判断。当你不知道具体规则该怎么写的时候,把判断权交给 LLM——AI 比规则更懂什么叫“完成”。

避坑 1:不要用 Hooks 做 lint 检查
那是 CI 的事。Hooks 里的 lint 结果不能实时反馈给 Claude(特别是 async hook),而且 Hooks 不应该做“代码质量评分”这种事——规则太主观,Claude 会学会绕过它们。

避坑 2:async hook 不是实时监控
systemMessage 不是实时消息,是下一次对话才反馈。如果 Claude 在测试跑完之前就结束了会话,测试结果不会被 Claude 看到。

避坑 3:matcher 写错等于 Hook 不触发
很多人配完 Hook 发现没效果,一查才发现 matcher 写错了——Write|Edit 写成了 write|edit(大小写敏感),或者想匹配文件路径却用了工具名。配完之后用已知操作验证一下触发条件。

到这里,Claude Code 的三大核心扩展机制都说完了:

  • CLAUDE.md:给 Claude 上下文,让它知道你在什么项目、什么约束、什么偏好
  • Skills:给 Claude 工具,让它能调用你定义的函数完成特定任务
  • Hooks:给 Claude 规则,让它在行动前后被你约束和验证

三者配合,Claude Code 才真正变成一个可控的团队成员——它知道该做什么(Skills),知道什么不能做(Hooks),知道在谁手下干活(CLAUDE.md)。对于想深入探索更多类似实践和讨论的开发者,也可以在 云栈社区 找到更多灵感和分享。

参考资料




上一篇:Attention Residual架构深度解析:如何权衡延迟优化与工程实现?
下一篇:GraphRAG新范式解析:LPG与RDF双架构如何提升金融数据检索精度
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-3-26 03:39 , Processed in 0.616429 second(s), 42 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表