钩子 Hooks
在工具调用前后插入自动检查,让流程自己守门
把"每次都要记得做的事"交给钩子,它永远不忘AI 能连外部系统了。这一章给它装上『自动守门』 —— 在工具调用前后自动执行检查,把团队规范沉淀为系统行为。
总在补救
很多问题是"如果当时检查一下就好了"
提交了密钥
AI 帮你改了配置文件,顺手把 API Key 写进去了,你点了确认,提交了——事后才发现泄露。
改完忘了跑测试
AI 改完代码你以为没事了,结果破坏了别处。如果每次改完自动跑测试,当时就能发现。
不规范代码进库
AI 写的代码没过 lint,混进了主分支。写入前自动 lint 就能拦住。
什么是 Hook
定义、白话、和普通脚本的区别
Hook 是 Qoder CLI 在特定事件发生时自动触发的 shell 命令。你预先配置好"当 X 事件发生时,执行 Y 脚本",之后每次 X 发生,Y 自动跑。
说白了,Hook 就像家里的烟雾报警器——它平时不动,但一旦检测到烟雾(事件),就自动响(执行脚本)。你不用每次做饭都盯着,它替你守着。
普通脚本
- 你主动执行
- 执行与否靠你记得
- 和 AI 工作流无关
Hook 脚本
- 事件触发自动执行
- 永远不漏,永远不忘
- 嵌入 AI 的工具调用流程
四类事件
Hook 能挂载到哪些时机
| 类别 | 典型事件 | 用途 |
|---|---|---|
| 工具事件 | PreToolUse(工具调用前)、PostToolUse(工具调用后) | 拦截/检查文件操作、命令执行 |
| 会话事件 | SessionStart(会话开始)、SessionEnd(会话结束) | 初始化环境、清理、通知 |
| 任务事件 | 任务开始、任务完成 | 进度通知、状态记录 |
| 生命周期 | 停止、中断等 | 收尾、保存状态 |
三种策略
Hook 脚本的退出码,决定接下来发生什么
Hook 脚本通过退出码(exit code)告诉 CLI 检查结果。三个码对应三种行为:
| 退出码 | 含义 | CLI 行为 |
|---|---|---|
0 |
通过 | 检查通过,继续执行原操作 |
1 |
提醒 | 给 AI/用户一个提示信息,但仍继续 |
2 |
阻断 | 阻止原操作,把错误反馈给 AI |
环境变量
Hook 脚本怎么知道"当前在操作什么"
CLI 触发 Hook 时,会通过环境变量把当前操作的上下文传给脚本。常用变量:
| 变量 | 含义 |
|---|---|
$CLAUDE_TOOL_NAME | 当前调用的工具名(如 Write、Bash) |
$CLAUDE_FILE_PATH | 操作的文件路径 |
| 其他上下文 | 命令内容、会话信息等 |
#!/bin/bash
# 一个 PreToolUse 钩子示例:写入前检查是否含密钥
if grep -E "(api_key|secret|password)" "$CLAUDE_FILE_PATH"; then
echo "检测到疑似密钥,已阻止写入"
exit 2 # 阻断
fi
exit 0 # 通过
实战:防密钥
PreToolUse 钩子——AI 写文件前扫描敏感信息
场景:防止 AI 把 API Key、密码写进代码。
# scripts/security-scan.sh
#!/bin/bash
# 检查即将写入的文件是否含敏感信息
PATTERNS="(api_key|apikey|secret|password|token)\s*[:=]"
if grep -iE "$PATTERNS" "$CLAUDE_FILE_PATH" 2>/dev/null; then
echo "⚠️ 检测到疑似敏感信息,写入已阻止"
echo "请确认是否真的需要写入这些内容"
exit 2
fi
exit 0
把这个脚本注册为 PreToolUse 钩子后,每次 AI 要写文件,都会先跑这个检查。命中敏感模式就阻断,AI 收到反馈会自己调整。
实战:跑测试
PostToolUse 钩子——AI 改完代码自动跑测试
场景:AI 每次修改源码后,自动跑相关测试,立即发现破坏。
# scripts/auto-test.sh
#!/bin/bash
# 只在修改的是源码文件时触发
case "$CLAUDE_FILE_PATH" in
*.ts|*.tsx|*.js|*.jsx)
echo "检测到代码变更,运行测试..."
if npm test 2>&1; then
echo "✅ 测试通过"
exit 0
else
echo "❌ 测试失败,请修复"
exit 2 # 反馈给 AI,让它修
fi
;;
*)
exit 0 # 非代码文件,跳过
;;
esac
实战:会话通知
SessionStart 钩子——会话开始时自动初始化
场景:每次开会话,自动拉取最新代码、提醒当前分支、检查环境。
# scripts/session-init.sh
#!/bin/bash
# 会话开始时的环境检查
echo "=== 会话环境 ==="
echo "当前分支: $(git branch --show-current 2>/dev/null)"
echo "未提交改动: $(git status --short | wc -l) 个文件"
echo "Node: $(node --version 2>/dev/null || echo '未安装')"
echo "================"
exit 0
更多 SessionStart 用途
- 自动
git pull拉最新 - 检查依赖是否安装
- 加载项目所需环境变量
- 播放提示音(macOS:
say "ready")
任务完成通知
长任务(如 Quest)跑完时,用任务事件钩子发通知:
# macOS 语音提醒
say "任务完成了"
# 或发桌面通知
osascript -e 'display notification \
"Qoder 任务完成" with title "Qoder"'
配置方式
Hook 怎么注册到 CLI
Hook 配置写在 settings 文件中,关联"事件 → 脚本":
{
"hooks": {
"PreToolUse": [
{
"matcher": "Write|Edit",
"command": "bash scripts/security-scan.sh"
}
],
"PostToolUse": [
{
"matcher": "Write|Edit",
"command": "bash scripts/auto-test.sh"
}
],
"SessionStart": [
{ "command": "bash scripts/session-init.sh" }
]
}
}
"Write|Edit" 只在写文件时触发;"Bash" 只在执行命令时触发。省略 matcher 则对所有工具触发。
chmod +x scripts/*.sh,否则钩子会因权限报错。Windows 用户用 .bat 或 .ps1 脚本。
排错指南
Hook 相关的高频问题
钩子没触发
原因:配置路径不对、matcher 不匹配、脚本无执行权限。修复:确认 settings 路径;matcher 写对工具名;chmod +x 脚本。
钩子报错影响正常使用
原因:脚本本身有 bug,exit 2 误阻断。修复:先在终端单独跑脚本验证;调试期临时把 exit 2 改成 exit 1(只提醒不阻断)。
钩子太慢拖慢工作流
原因:脚本做太多事(如全量测试)。修复:用 matcher 缩小触发范围;只跑相关测试;耗时操作放 PostToolUse 不阻塞。
误报太多
原因:检查模式太宽。修复:细化正则;维护白名单;逐步收紧而非一步到位。
排错指南
Hook 配置中的高频问题
Hook 没有触发
原因:事件类型不匹配、matcher 写错、或脚本路径不对。修复:检查事件名拼写(PreToolUse 不是 PreTool);用通配符 * 做 matcher 测试;用绝对路径。
Hook 脚本执行失败
原因:脚本无执行权限、语法错误、或依赖缺失。修复:chmod +x 脚本;在终端手动运行验证;检查 shebang 行(#!/bin/bash)。
exit 2 但没有反馈给 AI
原因:stderr 输出未包含可读的错误信息。修复:在脚本中用 echo "错误原因" >&2 输出到 stderr;CLI 会把 stderr 作为拒绝原因反馈给 AI。
环境变量在 Hook 中读不到
原因:CLI 注入的变量名拼写错误,或 shell 环境不同。修复:确认变量名( 而非 );在脚本开头 env | grep CLAUDE 调试。
动手练习
给你的项目装上两个守门员
练习 1:防密钥钩子
- 写一个 security-scan.sh,扫描敏感词
- 注册为 PreToolUse 钩子,matcher 为 Write|Edit
- 让 AI 试着写一个含 "password" 的文件,验证被阻断
练习 2:会话初始化钩子
- 写一个 session-init.sh 显示分支和状态
- 注册为 SessionStart 钩子
- 新开会话,观察自动输出的环境信息
练习 3(进阶):自动测试
- 写 auto-test.sh,改完代码自动跑测试
- 测试失败时 exit 2 反馈给 AI
- 观察 AI 是否主动修复
本章回顾
带走的三个核心认知
Hook 是自动守门员
事件触发自动执行脚本,永远不漏、永远不忘。
退出码决定行为
0 通过、1 提醒、2 阻断——exit 2 能拦下 AI 的危险操作。
环境变量传上下文
$CLAUDE_TOOL_NAME、$CLAUDE_FILE_PATH 让脚本知道当前操作。
下一章,学习把这些零散的能力(技能、钩子、MCP)打包成可分发的插件。
延伸阅读
继续本课程
- 下一章:插件 Plugins — 打包分发你的方案
- 权限系统 — 和 Hook 配合的安全边界