主题: 第17部分。Qwen Code 钩子:工作流自动化
难度等级: 中级
预计学习时间: 4-6 小时(理论:1.5 小时,实践:2.5-4.5 小时)
前置要求: 对 Qwen Code 架构和会话工作的基本理解
了解 JSON 格式并能够编辑配置文件
Python 编程技能(脚本编写水平)
理解课程前面部分中的 SDD(规范驱动开发)概念
命令行工作经验和基本 shell 命令
熟悉 QWEN.md、specs/mission.md、specs/tech-stack.md、specs/roadmap.md 文件
学习目标: 在课程示例的指导下,在学习项目中配置并连接最小钩子集(PreToolUse、PostToolUse、SessionStart、UserPromptSubmit)
区分何时适合使用钩子,何时只需将规则写入 QWEN.md 或 validation.md
实现防护性钩子以阻止危险命令,并清晰解释停止原因
以 JSONL 格式组织工具事件日志,采用异步工作模式
应用钩子安全规则:代码审查、超时、秘密隔离、受信任目录检查
概述: Qwen Code 钩子是一种工作流自动化机制,允许在会话的关键时刻运行脚本:启动时、用户请求后、工具使用前后、出错时或完成响应前。在 SDD 上下文中,钩子不会取代规范,而是使过程更少依赖人的记忆:提醒规则、记录事件、阻止危险操作、添加上下文片段、收集追溯线索。关键原则:钩子应该小巧、易懂且只解决一个任务。如果无法用一句话解释其用途——它做得太多了。课程涵盖三种主要钩子类型(command、http、function)、六个关键事件、连接示例的实践以及至关重要的安全规则。
关键概念: 事件 (event): 激活钩子的触发器。关键事件:SessionStart(会话启动)、UserPromptSubmit(用户请求后)、PreToolUse(工具使用前)、PostToolUse(成功使用后)、PostToolUseFailure(工具错误后)、Stop(完成响应前)、PreCompact(上下文压缩前)。每个事件在 SDD 过程中都有其特定语义。
匹配器 (matcher): 确定钩子是否应为特定事件触发的组件。允许精确配置钩子:例如,仅针对 Bash 工具的 PreToolUse,而非所有工具。
钩子执行器 (command/http/function): 可运行脚本的类型。Command — 本地命令(Python 脚本、shell 脚本),项目的主要选择。HTTP — 发送到外部地址进行集中日志记录。Function — 会话内部 JS 函数,普通项目中很少需要。
钩子输入 json: 通过 stdin 传递给钩子的数据:会话标识符、当前目录、事件名称、时间,对于工具还包括工具名称、输入数据、结果或错误。
钩子输出 json: 钩子通过 stdout 输出的结果。三个关键变体:空响应(不更改任何内容)、additionalContext(向上下文添加简短消息)、阻止操作并解释原因。
钩子退出码: 0 — 成功执行,2 — 阻塞性错误(停止操作),其他代码 — 非阻塞性错误(在调试中可见,但过程继续)。
异步模式: 钩子不阻塞 Qwen Code 主工作流程的模式。对于日志记录和后台检查至关重要,以免会话等待次要任务完成。
防护性钩子 (pretooluse guard): 在执行前阻止危险操作的钩子。危险模式示例:rm -rf / 或 ~、git reset --hard、git clean -fd、通过 sh 运行下载的脚本。不能替代沙箱,但作为最终检查。
工具日志记录 (posttooluse/posttoolusefailure): 记录工具使用事实以供后续追溯:崩溃频率、修改的文件、手动检查、转移到 validation.md 或 QWEN.md 的候选项。
SDD 上下文注入: 通过 SessionStart 和 UserPromptSubmit 添加关于项目的简短提醒,特别是在 /clear 之后。钩子检查关键文件(QWEN.md、specs/mission.md 等)的存在,并引导代理到真相来源,而不重复其内容。
钩子超时: 钩子的最大执行时间,防止会话被挂起的脚本阻塞。
钩子安全规则: 钩子以用户权限在其环境中执行。要求:钩子代码审查、超时、后台任务使用异步模式、秘密隔离、HTTP 的显式允许变量列表、禁止静默修改文件、阻止时提供清晰消息、自动启动前检查目录可信度。
练习题: 名称: 安装和基础钩子连接
问题: 将课程仓库中的钩子示例复制到学习项目中,设置执行权限,并连接 settings-hooks.example.json,而不覆盖现有的模型、授权和 MCP 服务器设置。
解决方案: 1. 创建目录:mkdir -p .qwen/hooks
- 复制三个钩子:cp sdd-qwen-code-ru/examples/hooks/pre_tool_guard.py .qwen/hooks/; cp sdd-qwen-code-ru/examples/hooks/log_tool_result.py .qwen/hooks/; cp sdd-qwen-code-ru/examples/hooks/inject_sdd_context.py .qwen/hooks/
- 设置执行权限:chmod +x .qwen/hooks/*.py
- 单独复制设置示例:cp sdd-qwen-code-ru/examples/hooks/settings-workflow.example.json .qwen/settings-hooks.example.json
- 手动将 settings-hooks.example.json 的内容与现有的 .qwen/settings.json 合并,保留 model、auth、mcp 部分
- 检查 JSON 有效性:python -m json.tool .qwen/settings.json
难度: 初级
名称: 为 Bash 配置防护性钩子
问题: 将 pre_tool_guard.py 仅连接到 Bash 工具的 PreToolUse 事件。在包含危险模式的安全测试命令上测试(例如,在测试仓库中执行 git reset --hard)。确保钩子解释阻止原因,而非简单禁止。
解决方案: 1. 在 .qwen/settings.json 的 hooks 部分添加匹配器为 {"event": "PreToolUse", "tool": "Bash"} 的记录,执行器为 {"type": "command", "command": ".qwen/hooks/pre_tool_guard.py"}
- 创建测试 git 仓库:mkdir /tmp/test-guard && cd /tmp/test-guard && git init && echo 'test' > file.txt && git add . && git commit -m 'init'
- 在 Qwen Code 中尝试通过 Bash 执行:git reset --hard HEAD~
- 检查钩子是否返回代码 2,Qwen Code 是否显示了停止原因并解释了具体危险模式
- 尝试安全替代方案:git log --oneline -5 并确认其通过
- 检查其他工具(Read、Edit)是否未被钩子影响
难度: 中级
名称: 异步模式日志记录
问题: 将 log_tool_result.py 连接到 PostToolUse 和 PostToolUseFailure 事件。配置异步模式,使日志记录不会减慢会话速度。执行多次工具操作并分析生成的日志。
解决方案: 1. 在 settings.json 中添加两个钩子,使用相同的执行器但不同事件:PostToolUse 和 PostToolUseFailure,command: .qwen/hooks/log_tool_result.py
- 为两个钩子设置 "async": true
- 启动 Qwen Code,执行 /clear,要求代理读取 roadmap 并提出下一个功能
- 检查日志创建:ls -la .qwen/hooks/logs/tool-events.jsonl
- 分析记录:jq . .qwen/hooks/logs/tool-events.jsonl | head -20
- 确保记录简洁:包含 event_type、tool_name、timestamp、success/failure,但不包含完整环境转储和秘密
- 检查工具错误时(例如读取不存在的文件)的记录是否与成功时不同
难度: 中级
名称: /clear 后的上下文注入
问题: 将 inject_sdd_context.py 连接到 SessionStart 和 UserPromptSubmit。检查 /clear 命令后,代理是否收到关于项目关键文件的简短提醒,而非完整的规范文本。
解决方案: 1. 在 settings.json 中为 SessionStart 和 UserPromptSubmit 添加钩子,命令为 .qwen/hooks/inject_sdd_context.py
- 确保项目中至少存在 QWEN.md 和 specs/mission.md
- 启动会话,执行 /clear
- 在下一个请求中询问:「项目中有哪些规范?」
- 检查代理是否提及文件存在,但不完整引用其内容
- 在 Qwen Code 调试模式下检查钩子日志或 additionalContext 输出
- 确保当 specs/roadmap.md 不存在时,钩子正确处理情况,不崩溃且不添加多余内容
难度: 中级
名称: 实践后的钩子分析与重构
问题: 完成练习 1-4 后,书面回答四个问题:哪个钩子真正帮助了流程;哪个造成了噪音;什么最好转移到 QWEN.md;什么最好保留为 validation.md 中的手动步骤。根据答案调整配置。
解决方案: 1. 执行练习时做笔记:记录钩子有用触发的时刻和造成干扰的时刻
- 有用触发示例:pre_tool_guard 阻止了 git reset --hard — 保留
- 噪音示例:inject_sdd_context 在每个 UserPromptSubmit 时触发并重复 QWEN.md 中的信息 — 考虑将「首先阅读 QWEN.md」规则转移到 QWEN.md 本身,仅对 SessionStart 保留钩子
- QWEN.md 示例:「/clear 后注意 specs/roadmap.md」— 这是行为规则,不需要自动化
- validation.md 示例:「提交前运行 npm test」— 重型检查,不适合钩子
- 根据分析调整 settings.json,删除或修改钩子,在提交注释中记录决策
难度: 高级
案例研究: 名称: 保护生产数据库免受意外删除
场景: 一个五人开发团队使用 Qwen Code 处理 Node.js 微服务架构。项目包含带有生产级数据的 Docker 容器用于本地测试。开发者在调试时不小心要求代理执行 docker volume prune,这将导致包含测试数据的卷被删除,恢复需要 2-3 小时。
挑战: QWEN.md 中的规则无法在关键时刻阻止危险命令:处于心流状态的开发者可能未注意到警告。代理对每条 Bash 命令的手动检查不可靠。需要自动但可解释的障碍来阻止破坏性操作,同时不影响正常工作。
解决方案: 团队部署了防护性钩子 pre_tool_guard.py,扩展了其模式:添加了 docker volume prune、docker system prune -a、在包含 .env 文件的目录中执行 rm、任何包含 DROP DATABASE 的命令。钩子仅针对 PreToolUse + Bash 配置。阻止时,钩子返回 JSON 解释:「命令 docker volume prune 将删除所有未使用的 Docker 卷,包括项目 X 的测试数据卷。要安全清理,请使用 docker volume rm 指定具体卷名,或通过 validation.md 步骤「手动清理 Docker 资源」确认。」开发者看到原因并可以选择安全路径。
结果: 三个月内,钩子阻止了 12 条潜在破坏性命令。10 次中开发者选择了安全替代方案。2 次中有意识地通过 validation.md 执行。测试数据恢复时间从 2-3 小时缩短到零。钩子成为新开发者入职检查清单的一部分。
经验教训: 没有解释的阻止会引发绕过尝试;具体原因引导至安全解决方案
钩子应该高度专一:保护 Docker 命令不妨碍正常的 git 和文件操作
钩子与 validation.md 的整合建立了自动检查和手动检查之间的联系,不破坏流程
相关概念: 防护性钩子 (PreToolUse guard)
钩子退出码
带原因解释的钩子输出 JSON
钩子与 validation.md 的关联
名称: 钩子过多与回归简洁
场景: 一个三人团队热情地在所有可用事件中部署了钩子:SessionStart、UserPromptSubmit、PreToolUse、PostToolUse、PostToolUseFailure、Stop、PreCompact。每个钩子执行 3-4 个任务:日志记录、检查、添加上下文、格式化。Qwen Code 会话耗时增加 2.5 倍,代理因过载开始忽略部分上下文,开发者不再理解系统的某些反应来自何处。
挑战: 自动化的经典陷阱:钩子不再是透明工具,而变成了隐藏逻辑。团队无法用一句话解释会话行为。调试需要跟踪 7 个钩子,其中许多相互冲突:一个添加上下文,另一个修改它,第三个记录已更改的版本。
解决方案: 团队按照「一句话—一个钩子」原则进行审计。对每个钩子提问:「它一句话是做什么的?」未通过检查的钩子被拆分或删除。最终配置:SessionStart + inject_sdd_context(/clear 后提醒项目)、PreToolUse + pre_tool_guard 仅针对 Bash(阻止危险操作)、PostToolUseFailure + log_tool_result async(保存错误供追溯)。其余转移到 QWEN.md 或 validation.md。
结果: 会话时间恢复到原始水平。透明度恢复:任何开发者都能在 30 秒内解释行为。错误日志变得清晰且对每周追溯有用。「项目不超过三个钩子」规则纳入团队指南。
经验教训: 钩子应该解决一个任务并用一句话解释;否则它会成为技术债务
从一两个钩子开始,而非全部启用 — 否则过程不透明
日志记录、检查、格式化和安全不应混合在一个脚本中
相关概念: 「一句话—一个钩子」规则
异步模式
不应自动化的内容
钩子与 QWEN.md 和 validation.md 的关联
学习建议: 严格从一个钩子开始实践 — 最好是 PreToolUse guard,因为其益处直观且风险明确。只有在深思熟虑分析第一个后才添加其他。
实践期间维护「钩子日记」:记录每次触发、反应时间、是否有益或造成噪音。这将简化最终的重构练习。
使用 jq 或 python -m json.tool 读取 tool-events.jsonl 日志 — JSONL 格式专门用于逐行处理,不要尝试将其作为普通 JSON 数组读取。
创建测试「沙箱」来检查防护性钩子:单独的 git 仓库、带无用数据的 Docker 卷、临时文件。永远不要在真实系统上测试 rm -rf / 的阻止功能。
比较钩子在同步和异步模式下的行为:先不带 async 运行 log_tool_result.py,测量工具间的延迟,然后启用 async 进行比较。这将可视化关键差异。
对于视觉型学习者,在纸上画出课程中的示意图(事件 → 匹配器 → 钩子 → 决策 → 继续/停止/上下文)并标记你的配置钩子经过哪些路径。
对于听觉型学习者:在将每个钩子添加到 settings.json 之前,大声用一句话说出其用途。如果句子变长或包含「和」连词,拆分该钩子。
定期重读「不应自动化的内容」部分 — 这是每次新钩子前的自检清单。
练习手动合并 settings.json:这是一项在课程更新和与他人仓库工作时需要的技能,其中钩子可能是隐藏威胁。
与同事或学习小组讨论:你的技术栈中哪些命令被视为「危险」?列表因技术而异:Python(pip install 未知包)、Node.js(npm audit fix --force)、Go(go get 替换模块)、DevOps(kubectl delete、terraform destroy)。
附加资源: 课程示例仓库 (sdd-qwen-code-ru/examples/hooks/): 源文件 pre_tool_guard.py、log_tool_result.py、inject_sdd_context.py、settings-workflow.example.json 和 README.md — 所有实践练习的基础
Qwen Code 钩子官方文档: 事件、执行器类型、JSON 格式的扩展描述(检查版本时效性)
Jq — JSON 命令处理器: https://jqlang.github.io/jq/ — 终端中分析 JSONL 日志的工具
Python json.tool: 用于验证和格式化 JSON 的内置模块:python -m json.tool 文件.json
「十二要素应用」文章(config 部分): 配置和秘密存储原则,适用于 HTTP 钩子设置和环境变量
OWASP 速查表:注入防护: 防止注入的建议,与钩子输入处理和安全命令构建相关
课程前面部分(qwen.md、specs/、validation.md): SDD 上下文,正确区分钩子和规范所需
摘要: Qwen Code 钩子是一种强大但需要纪律的 SDD 流程自动化工具。其目的是降低对人类记忆的依赖,而非取代规范。关键原则:从 1-2 个钩子开始,每个钩子解决一个任务并用一句话解释,防护性钩子解释阻止原因,日志记录异步工作,检查按权重分配(轻量—自动,重量—通过 validation.md)。钩子安全至关重要:它们以用户权限执行,需要审查、超时、秘密隔离和处理他人仓库时的谨慎。掌握钩子后,学生准备好学习后续课程部分的钩子安全和 SQLite 记忆。