乐于分享
好东西不私藏

第15章:防患未然——用Hooks给AI助手装上自动防护盾

第15章:防患未然——用Hooks给AI助手装上自动防护盾

大家好,我是伦哥。

前面我们学习了Skills的完整知识体系——从知识工程到系统集成,从按需加载到跨平台通用。Skills告诉Claude“怎么做”,Commands告诉Claude“做什么”。但有一个关键问题它们都解决不了——Claude能不能做?

今天我们进入一个全新的领域——Hooks,事件驱动自动化。它是Claude Code三大扩展机制中唯一能拦截和修改Claude行为的机制,也是工程化实践中安全防线的最后一道闸门。


一、为什么需要Hooks?

想象这样一个场景:伦哥用Claude Code快速写完了代码,测试通过,git push,然后关机睡觉。

这种AI辅助下的快速迭代,代价有时候是灾难性的。安全漏洞、敏感信息泄露、代码格式混乱、测试没跑就上线……这些问题的共同点是:它们本可以被自动阻止。

如果有一道看不见的防线,在代码提交前自动检查敏感文件、在文件保存后自动格式化、在Claude完成任务时自动运行测试——那些“忘记”就不再是问题。

这道防线,就是Hooks。


二、Hooks的本质——AI时代的中间件

如果你有Web开发经验,你一定熟悉中间件(Middleware)的概念。

中间件在请求到达最终处理函数之前插入检查和处理,实现横切关注点。这些逻辑不属于任何一个业务功能,但又必须贯穿所有请求——认证要每个接口都检查,日志要每个操作都记录,限流要每个入口都控制。

Claude Code的Hooks机制与此异曲同工,但它针对的不是HTTP请求,而是AI Agent的工具调用。

Hooks是AI助手的中间件——拦截、监控、增强每一次交互。这个类比不仅是形象上的相似。Web中间件解决的核心问题是“业务代码不应该操心安全和日志”,Hooks解决的核心问题也一样——Claude不应该操心格式化和权限检查,它只管写好代码就行。安全防线、质量守卫、审计日志,全部由Hooks在“幕后”自动完成。


三、Commands、Skills、Hooks的定位差异

和Commands、Skills相比,Hooks是三者中唯一能拦截和修改Claude行为的机制。

特性
Commands
Skills
Hooks
触发方式
用户手动/command
Claude自动发现
事件自动触发
执行时机
用户请求时
Claude判断需要时
特定事件发生时
主要用途
标准化操作
领域知识
自动化和安全控制
控制能力
执行任务
指导行为
拦截/修改/增强

这三者构成了一个完整的控制谱系:

  • Commands告诉Claude做什么 —— 用户显式发起
  • Skills告诉Claude怎么做 —— Claude自主调用
  • Hooks告诉Claude能不能做 —— 用户请求时

如果把Claude比作一个工程师,Commands是你给他下达的任务指令,Skills是他掌握的领域知识,而Hooks是公司的安全制度和质量规范——不管你做什么任务,用什么知识,这些制度都在背后默默运行。


四、17种Hook事件——完整生命周期覆盖

截至2026年3月,根据Anthropic官方文档,Claude Code支持17种Hook事件,覆盖了从会话启动到结束的完整生命周期:

事件
触发时机
能否阻止?
典型用途
SessionStart
会话开始或恢复时
否
环境初始化、注入上下文
UserPromptSubmit
用户提交输入后,Claude处理前
能
输入预处理、输入校验
PreToolUse
工具执行前
能
权限检查、危险操作拦截
PermissionRequest
权限对话框弹出时
能
自动批准或拒绝权限请求
PostToolUse
工具执行成功后
否
格式化、验证、日志记录
PostToolUseFailure
工具执行失败后
否
错误日志、告警
Notification
Claude发送通知时
否
自定义通知方式
SubagentStart
子代理启动时
否
为子代理注入上下文
SubagentStop
子代理完成时
能
验证任务结果、强制继续
Stop
Claude完成响应时
能
质量检查、自动测试
PreCompact
上下文压缩前
否
压缩时的预处理
SessionEnd
会话结束时
否
清理资源、记录统计

17个事件乍看数量不少,但设计逻辑非常清晰——按照“能否阻止”这一列来看,整个事件体系分为三大阵营:

🛡️ 控制点——能阻止的事件(PreToolUse、UserPromptSubmit、Stop、SubagentStop):你可以通过它们改变Claude的执行路径——拦截危险操作、拒绝不合理的输入、强制Claude继续修复。它们是Hooks系统的肌肉。

🤖 接管点——替代默认行为的事件(PermissionRequest):它不是简单地阻止,而是接管了原本由用户手动处理的权限弹窗——你的脚本可以自动批准或拒绝权限请求,替代人类的决策。它是Hooks系统的自动驾驶。

👁️ 观察点——不能阻止的事件(SessionStart、PostToolUse等):你只能在这些时刻做记录、做反馈、做后处理,但不能改变已经发生的事情。它们是Hooks系统的眼睛。

这种不对称设计是有意为之的。工具执行前可以拦截,因为操作还没发生,拦截不会造成不一致状态。工具执行后不能拦截,因为操作已经完成——你不能“取消”一个已经写入磁盘的文件。但你可以观察它、记录它、反馈它。

伦哥建议:在实践中,你不需要记住全部17种事件。把PreToolUse(守门员)、PostToolUse(质量守卫)、Stop(质量门控)这三个记牢就够了。它们覆盖了80%以上的自动化需求。


五、Hook配置详解

Hooks可以在多个位置配置,每个位置有不同的作用域和共享策略:

配置位置
作用域
是否提交Git
典型用途
~/.claude/settings.json
用户级
否
个人习惯(日志格式、通知方式)
.claude/settings.json
项目级
是
团队约定(格式化规则、敏感文件保护)
.claude/settings.local.json
本地覆盖
否
临时覆盖团队配置
子代理frontmatter
子代理级
是
子代理专属Hook

一个典型的Hook配置长这样:

{
"hooks": {
"PreToolUse": [
      {
"matcher": "Bash",
"hooks": [
          {
"type": "command",
"command": "./hooks/block-dangerous.sh"
          }
        ]
      }
    ],
"PostToolUse": [
      {
"matcher": "Write",
"hooks": [
          {
"type": "command",
"command": "prettier --write $CLAUDE_FILE_PATH"
          }
        ]
      }
    ]
  }
}

这个JSON结构有三层嵌套:

  • 第一层:事件类型(什么时候触发)——PreToolUse还是PostToolUse?
  • 第二层:匹配器(针对哪个工具)——Bash、Write还是所有工具?
  • 第三层:Hook列表(执行什么)——执行脚本、调用LLM还是启动子代理?

三层决策,层层收窄,最终精准地把正确的检查逻辑应用到正确的时机和工具上。

Matcher匹配模式

Matcher支持四种匹配模式:

模式
示例
说明
精确匹配
"Write"
匹配单个工具
多工具匹配
"Edit|Write|MultiEdit"
用竖线分隔
匹配所有
"*"
所有工具(谨慎使用)
空匹配
""
用于生命周期事件

对于Notification、Stop、SubagentStop等生命周期事件,matcher会被忽略——这些事件不针对特定工具。


六、四种Hook执行类型

当一个Hook被触发后,其具体执行方式有四种:

1. Command类型——执行Shell脚本

最常用、最可靠的类型。确定性规则永远比LLM判断更可靠——同样的输入永远产生同样的输出。

{
"type": "command",
"command": "./hooks/check-security.sh",
"timeout": 30000
}

2. Prompt类型——LLM评估

当规则无法用确定性脚本表达时,就需要LLM的判断力。比如“这段代码是否有安全隐患”——这种判断需要理解代码语义。

{
"type": "prompt",
"prompt": "Evaluate if this task was completed correctly."
}

3. Agent类型——子代理评估

最强大也最“重”的评估方式。Agent Hook会启动一个子代理,可以使用Read、Grep、Glob等工具来验证条件——不只是“看一眼就判断”,而是可以“翻代码确认”。

{
"type": "agent",
"prompt": "Verify that all unit tests pass.",
"timeout": 120
}

4. HTTP类型——远程服务调用

把事件数据以POST请求发送到远程HTTP端点,由远程服务返回决策结果。适合团队共享审计服务、集中式安全扫描等场景。

选择策略

能用command的不用prompt,能用prompt的不用agent。确定性规则永远比LLM判断更可靠,LLM判断比子代理执行更快。

类型
适用场景
可靠性
command
确定性规则(模式匹配、文件检查)
最高
prompt
需要判断力但输入数据足够决策
中等
agent
需要检查实际代码/文件状态
取决于子代理质量
http
对接远程服务
取决于远程服务

七、PreToolUse:工具执行前的守门员

PreToolUse是最强大的Hook事件,因为它能阻止工具执行。它就像机场的安检门——在你登机(工具执行)之前,先过一道检查。

PreToolUse Hook可以做三件事:

  • allow:放行
  • deny:拦截
  • updatedInput:改写输入参数后再执行

第三种能力特别有趣——你不仅能“放行或拦截”,还能“偷偷改参数”。比如用户要执行rm -rf /tmp/test,你可以把它改成rm -rf /tmp/test --dry-run,先看看会删掉什么再说。

Hook脚本的通信协议

每个Hook脚本通过stdin接收一个JSON对象:

{
"session_id": "abc123",
"hook_event_name": "PreToolUse",
"tool_name": "Bash",
"tool_input": {
"command": "rm -rf /tmp/test"
  }
}

Hook脚本通过退出码和stdout JSON告诉Claude下一步做什么:

  • exit 0:放行
  • exit 2:阻止(只有exit 2才表示“我检查过了,这个操作确实危险”)
  • 其他非零退出码:脚本出错但不阻止

实战案例1:阻止危险命令

每个工程团队都有一些“绝对不能执行”的命令。rm -rf /会删除整个文件系统,git push --force origin main会覆盖远程主分支的历史。这些命令的共同特点是:一旦执行就无法挽回。

下面这个脚本用模式匹配来拦截这些灾难性命令:

#!/bin/bash
# block-dangerous.sh

INPUT=$(cat)
COMMAND=$(echo"$INPUT" | jq -r '.tool_input.command // ""')

DANGEROUS_PATTERNS=(
"rm -rf /"
"rm -rf /*"
"git push --force origin main"
"git push --force origin master"
"DROP DATABASE"
"DROP TABLE"
"curl.*| sh"
"wget.*| bash"
)

for pattern in"${DANGEROUS_PATTERNS[@]}"; do
if [[ "$COMMAND" == *"$pattern"* ]]; then
    cat <<EOF
{
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "deny",
"permissionDecisionReason": "Blocked dangerous command pattern: $pattern"
  }
}
EOF
exit 2
fi
done

echo'{}'
exit 0

当Claude试图执行危险命令时,你会在终端看到类似这样的输出:

Hook blocked tool call: Blocked dangerous command pattern: rm -rf /
This command could cause irreversible damage.

Claude收到拦截信息后会自动调整策略——换一种更安全的方式来完成你的请求。

实战案例2:保护敏感文件

另一个常见需求是保护敏感文件(如.env文件)不被Claude修改或读取:

#!/bin/bash
# protect-files.sh

INPUT=$(cat)
FILE_PATH=$(echo"$INPUT" | jq -r '.tool_input.file_path // ""')

PROTECTED_PATTERNS=(
".env"
".env.*"
"credentials.json"
"secrets.yaml"
"*.pem"
"*.key"
"id_rsa"
"kubeconfig"
)

for pattern in"${PROTECTED_PATTERNS[@]}"; do
if [[ "$FILE_PATH" == *"$pattern"* ]]; then
    cat <<EOF
{
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "deny",
"permissionDecisionReason": "Cannot modify sensitive file: $FILE_PATH"
  }
}
EOF
exit 2
fi
done

echo'{}'
exit 0

配置时,这个Hook要同时匹配Write和Edit两个工具:

{
"hooks": {
"PreToolUse": [
      {
"matcher": "Write|Edit",
"hooks": [
          {
"type": "command",
"command": "./hooks/protect-files.sh"
          }
        ]
      }
    ]
  }
}

安全原则:宁可误拦截,不可漏放行。安全场景下,误拦截的代价是用户手动确认一下,漏放行的代价可能是密钥泄露。


八、PostToolUse:工具执行后的质量守卫

PostToolUse在工具成功执行后运行。它不能阻止已经发生的操作,但它可以做三件更重要的事情:

  • 后处理:格式化、清理
  • 反馈:向Claude提供lint结果、警告
  • 记录:写入审计日志

PostToolUse最强大的能力在于通过additionalContext向Claude反馈信息:

{
"hookSpecificOutput": {
"hookEventName": "PostToolUse",
"additionalContext": "ESLint found 3 errors in the file you just wrote."
  }
}

additionalContext的内容会被注入到Claude的上下文中,Claude会看到这条反馈并据此调整行为。这不是简单的日志记录,而是一个闭环反馈机制——Hook观察到问题,反馈给Claude,Claude自动修复。

实战案例1:自动格式化

每次Claude写入或修改文件后,自动运行格式化工具:

#!/bin/bash
# auto-format.sh

INPUT=$(cat)
FILE_PATH=$(echo"$INPUT" | jq -r '.tool_input.file_path // ""')

EXTENSION="${FILE_PATH##*.}"

case"$EXTENSION"in
  js|jsx|ts|tsx|json|md|css|scss|html)
ifcommand -v npx &>/dev/null; then
      npx prettier --write "$FILE_PATH"
fi
    ;;
  py)
ifcommand -v black &>/dev/null; then
      black "$FILE_PATH"
fi
    ;;
  go)
ifcommand -v gofmt &>/dev/null; then
      gofmt -w "$FILE_PATH"
fi
    ;;
esac

echo'{}'
exit 0

这个Hook的美妙之处在于,Claude不需要知道项目用什么格式化工具。无论是Prettier、Black、gofmt还是rustfmt,只要本地安装了,就会自动应用。

实战案例2:自动Lint检查

格式化解决了“代码长什么样”的问题,Lint检查解决的是“代码有没有问题”:

#!/bin/bash
# lint-check.sh

INPUT=$(cat)
FILE_PATH=$(echo"$INPUT" | jq -r '.tool_input.file_path // ""')

if [[ "$FILE_PATH" == *.js || "$FILE_PATH" == *.ts ]]; then
  LINT_RESULT=$(npx eslint "$FILE_PATH" 2>&1) || true
  LINT_EXIT_CODE=$?

if [ $LINT_EXIT_CODE -ne 0 ]; then
    ESCAPED_RESULT=$(echo"$LINT_RESULT" | head -30 | jq -Rs '')
    cat <<EOF
{
"hookSpecificOutput": {
"hookEventName": "PostToolUse",
"additionalContext": "ESLint found issues:\n$ESCAPED_RESULT"
  }
}
EOF
fi
fi

echo'{}'
exit 0

这创造了一个自动化的质量循环:Claude修改文件 → PostToolUse触发 → Lint检查 → 发现问题 → 反馈给Claude → Claude自动修复 → 再次触发PostToolUse → 再次检查……直到所有Lint错误消除。

实战案例3:审计日志

对于金融、医疗、政府等合规性要求高的场景,记录Claude的所有操作至关重要:

#!/bin/bash
# audit-log.sh

INPUT=$(cat)
LOG_FILE="${CLAUDE_PROJECT_DIR:-.}/.claude/audit.log"

mkdir -p "$(dirname "$LOG_FILE")"

TIMESTAMP=$(date -Isseconds)
TOOL_NAME=$(echo"$INPUT" | jq -r '.tool_name' // "unknown")
TOOL_INPUT=$(echo"$INPUT" | jq -c '.tool_input' // {})

echo"[$TIMESTAMP] $TOOL_NAME: $TOOL_INPUT" >> "$LOG_FILE"

echo'{}'
exit 0

配置时用matcher: "*"匹配所有工具:

{
"hooks": {
"PostToolUse": [
      {
"matcher": "*",
"hooks": [
          {
"type": "command",
"command": "./hooks/audit-log.sh"
          }
        ]
      }
    ]
  }
}

审计日志的价值不在当下,而在未来。当你某天需要回答“上周三Claude到底改了什么导致了这个bug”时,审计日志就是你的时光机。


九、总结

这一讲,我们学习了Hooks的基础概念并给出了最常用的两个事件——PreToolUse和PostToolUse的大量示例。

Hooks的本质是AI Agent的中间件。就像Web开发中的中间件可以拦截HTTP请求一样,Hooks可以在Claude执行工具前后插入自定义逻辑。Claude不需要知道有Hook在运行,它只管专注于完成任务,安全防线、质量守卫、审计日志全部由Hooks在“幕后”自动完成。

Claude Code支持17种Hook事件,覆盖完整生命周期。它们分为控制点(能阻止)、接管点(替代默认行为)和观察点(只能记录)三大阵营。

四种Hook类型:

  • command:确定性规则,最可靠
  • prompt:单次LLM评估,需要判断力时使用
  • agent:多轮验证,需要翻代码才能决策时使用
  • http:对接外部服务

PreToolUse实战:阻止危险命令(rm -rf /、git push --force origin main)和保护敏感文件(.env、*.pem、credentials.json)。

PostToolUse实战:自动格式化、自动lint检查、审计日志。additionalContext字段创造了一个闭环反馈机制——Hook观察到问题,反馈给Claude,Claude自动修复。

现在回想开头伦哥发生的悲剧,用一个简单的PreToolUse Hook就能避免。自动化不是为了替代人的判断,而是为了在人类最容易出错的时刻——疲劳、赶工、分心——提供一道可靠的防线。


本系列往期文章:


如果我的文章对你有帮助,请帮忙点赞、在看、转发一下,你的支持是我持续输出高质量文章的最大动力,非常感谢!