前言
你是不是每次改完代码都要手动跑一遍测试、格式化、lint检查?或者每次PR合并后都要手动去更新文档、发通知、部署?这些重复的手工操作,明明可以让AI自动替你完成。
上篇我们优化了大型项目的上下文管理,AI在处理百万行代码时也能保持清醒了。但现在还有一个“效率黑洞”没解决——大量重复的手工操作依然占据着你的时间。代码提交后的测试、文件修改后的格式化、会话结束后的通知……这些能不能让OpenCode自动完成?
建议先点个关注,收藏这个专栏,这篇我们来用OpenCode的事件钩子系统构建自动化工作流——让AI在文件修改后自动跑测试、在会话结束后自动发通知、在工具调用前自动做安全检查,真正实现“写代码,剩下的交给OpenCode”。
上篇回顾
上篇我们构建了四层上下文管理体系——用/compact做被动压缩、用ACP做智能剪枝、用DCP做自动清理、用Context Manager做预索引,大型项目中的AI上下文终于不再“爆炸”了。
现在AI的“脑子”够用了,但手脚还不够勤快。每次你做完一件事,还得手动触发下一件事——改完代码要手动跑测试、写完文档要手动提交、会话结束要手动通知。本篇就是给OpenCode装上“自动化的手脚”——让它在合适的时机自动执行合适的动作。
环境与前置说明
本篇依赖上篇的产出成果:
- OpenCode已安装并可用
- 熟悉插件开发的基本流程(
event钩子) - 了解TypeScript/JavaScript插件编写
本篇会用到以下插件:
# YAML Hooks插件——声明式自动化(推荐入门)opencode plugin opencode-yaml-hooks-gf# 或者手动安装(如果上述命令不生效)bunaddopencode-yaml-hooks
opencode-yaml-hooks是目前最成熟的声明式钩子方案。它通过hooks.yaml文件配置自动化规则,不需要写TypeScript代码,适合90%的自动化场景。如果你需要更复杂的逻辑,也可以用TypeScript插件直接订阅event钩子。
文章目录
- 前言
- 上篇回顾
- 环境与前置说明
- 核心内容
- 第一步:理解“事件钩子”到底是什么
- 第二步:安装opencode-yaml-hooks——零代码自动化
- 第三步:配置第一个自动化规则——文件修改后自动格式化
- 第四步:配置工具执行前后的钩子——安全门禁
- 第五步:配置会话生命周期钩子——会话开始/结束自动化
- 第六步:高级钩子配置——scope、runIn与async
- 第七步:综合实战——构建完整的自动化工作流
- 异常处理与常见坑
- 报错1:YAML格式错误导致钩子不生效
- 报错2:`action: stop`不生效,危险操作仍然执行
- 报错3:`file.changed`钩子在文件修改后没有触发
- 本章产出总结
- 作者互动与资源引导
- 下篇预告
核心内容
第一步:理解“事件钩子”到底是什么
目标:搞清楚OpenCode的事件钩子系统是什么,以及它能用来做什么自动化。
你可能会问:事件钩子不就是监听事件吗?跟前面学的event钩子有什么区别?
OpenCode的事件钩子系统本质上是同一个东西——插件通过event钩子订阅系统事件。但“事件钩子”这个概念,在OpenCode生态里有两种不同的使用方式:
方式一:声明式YAML钩子(opencode-yaml-hooks)
在hooks.yaml文件中定义“当X事件发生时,执行Y动作”。不需要写代码,适合快速配置自动化规则。
方式二:程序式TypeScript钩子
在TypeScript插件中直接订阅event钩子,用代码实现复杂的自动化逻辑。
OpenCode的事件系统基于中央总线(event bus)运作:
- 组件发布事件(如
session.created、file.edited、tool.execute.before) - 总线将事件分发给所有订阅者
- 插件通过
event钩子消费事件
你可以在插件中订阅33种以上的系统事件,覆盖会话生命周期、文件变更、消息流、工具执行、LSP诊断等全场景。
注意了:声明式YAML钩子和程序式TypeScript钩子不是互斥的。你可以同时使用两者——用YAML钩子处理简单的文件变更自动化,用TypeScript插件处理复杂的业务逻辑。
运行验证:这一步不需要跑代码。你只需要记住一个核心概念——事件钩子 = “当X发生时,自动做Y”。
第二步:安装opencode-yaml-hooks——零代码自动化
目标:安装YAML Hooks插件,让自动化配置像写配置文件一样简单。
声明式YAML钩子是入门自动化的最快方式。你不用写一行TypeScript代码,只需要在一个YAML文件里描述“什么事件触发什么动作”。
安装方式:
# 通过opencode plugin命令安装opencode plugin opencode-yaml-hooks-gf# 或者用bun直接安装(如果上述命令不生效)bunaddopencode-yaml-hooks然后在opencode.json中注册插件:
{"$schema":"https://opencode.ai/config.json","plugin":["opencode-yaml-hooks"]}创建钩子配置文件。YAML Hooks支持全局和项目两个位置:
| 类型 | 路径 | 作用范围 |
|---|---|---|
| 全局 | ~/.config/opencode/hook/hooks.yaml | 所有项目生效 |
| 项目 | <项目根目录>/.opencode/hook/hooks.yaml | 仅当前项目生效 |
注意了:全局钩子先加载,项目钩子后加载,项目钩子可以覆盖或扩展全局钩子。
运行验证:安装完成后,在TUI中输入/hooks(如果插件支持该命令),或者检查插件是否在列表中:
opencode plugin list|grepyaml-hooks如果能看到opencode-yaml-hooks,说明安装成功。
第三步:配置第一个自动化规则——文件修改后自动格式化
目标:配置一个file.changed钩子,让OpenCode在文件修改后自动运行代码格式化。
这是最常用、也最安全的自动化场景——你改完代码,OpenCode自动帮你格式化。
创建项目级钩子配置文件:
mkdir-p.opencode/hooktouch.opencode/hook/hooks.yaml在.opencode/hook/hooks.yaml中写入:
# .opencode/hook/hooks.yaml# 钩子规则列表hooks:# 规则1:代码文件修改后自动格式化-event:file.changed# 触发事件:文件被修改conditions:# 条件:只匹配代码文件-matchesCodeFiles# 内置条件,匹配 .ts, .js, .py 等actions:# 动作:执行格式化-bash:"npx prettier --write {{ .Paths }}"# 用Prettier格式化修改的文件# 规则2:TypeScript文件修改后自动运行类型检查-event:file.changedconditions:-matchesAnyPath:"**/*.ts"# 匹配所有TypeScript文件-matchesAnyPath:"**/*.tsx"# 匹配所有TSX文件actions:-bash:"npx tsc --noEmit"# 运行TypeScript类型检查逐行解释一下:
event: file.changed:当任何文件被修改时触发conditions:可选的条件过滤,只有满足条件才执行动作matchesCodeFiles:内置条件,匹配常见的代码文件扩展名matchesAnyPath:自定义路径匹配,支持glob模式actions:要执行的动作列表,可以是bash命令、command命令或tool调用{{ .Paths }}:模板变量,会被替换为实际修改的文件路径列表
注意了:
file.changed是最干净的文件级钩子,适合linting、格式化、测试选择、索引和原子提交等工作流。它会自动去重——同一个文件在短时间内多次修改,只会触发一次钩子。
运行验证:保存配置文件后,在项目中修改一个代码文件(比如改一行代码然后保存)。观察终端——你应该能看到Prettier自动运行,并且修改的文件被格式化了。
第四步:配置工具执行前后的钩子——安全门禁
目标:在AI调用危险工具(如bash、edit)前后插入安全检查或日志记录。
tool.before.*和tool.after.*钩子让你在AI执行工具的前后插入自定义逻辑。
在.opencode/hook/hooks.yaml中添加:
hooks:# 之前的规则保持不变...# 规则3:禁止读取.env文件-event:tool.before.read# 在read工具执行前触发action:stop# 阻止工具执行conditions:-matchesAnyPath:"**/.env"# 匹配.env文件-matchesAnyPath:"**/.env.*"# 匹配.env.local等actions:-bash:|echo "❌ 安全策略:禁止读取 .env 文件" exit 2 # 退出码2触发action: stop# 规则4:危险命令执行前记录审计日志-event:tool.before.bash# 在bash工具执行前触发actions:-bash:|echo "[AUDIT] $(date): AI 执行命令: {{ .Command }}" echo "[AUDIT] $(date): AI 执行命令: {{ .Command }}" >> .opencode/audit.log# 规则5:文件修改后自动运行测试-event:tool.after.edit# 在edit工具执行后触发conditions:-matchesCodeFilesactions:-bash:"npm test -- --findRelatedTests {{ .Paths }}"逐行解释一下:
tool.before.read:在read工具执行前触发action: stop:配合bash脚本的exit 2,可以阻止工具执行tool.before.bash:在bash工具执行前触发,适合审计和命令过滤tool.after.edit:在edit工具执行后触发,适合运行测试或索引{{ .Command }}:模板变量,在tool.before.bash中表示要执行的命令
注意了:
action: stop仅在tool.before.*钩子上有效,且需要bash脚本以exit 2退出。如果脚本以其他状态码退出,钩子会继续执行但不会阻止工具。
运行验证:配置完成后,在TUI中让AI“读取.env文件的内容”。AI应该会收到错误提示,无法读取该文件。再让AI执行一个bash命令(如ls -la),检查.opencode/audit.log中是否出现了审计记录。
第五步:配置会话生命周期钩子——会话开始/结束自动化
目标:在会话创建、删除、空闲时触发自动化动作。
会话级别的钩子让你在会话的整个生命周期中插入自动化逻辑。
在.opencode/hook/hooks.yaml中添加:
hooks:# 之前的规则保持不变...# 规则6:会话创建时加载项目上下文-event:session.created# 新会话创建时触发actions:-bash:|echo "📝 新会话已创建: $(date)" echo "📝 新会话已创建: $(date)" >> .opencode/session.log# 规则7:会话空闲时自动生成总结-event:session.idle# 会话变为空闲时触发actions:-bash:|echo "✅ 会话完成: $(date)" # 可以在这里触发通知、提交代码等# 规则8:会话删除时清理临时文件-event:session.deleted# 会话被删除时触发actions:-bash:"rm -rf .opencode/temp/*"# 清理临时文件逐行解释一下:
session.created:新会话创建时触发session.idle:会话变为空闲时触发(注意:session.idle已弃用,建议用session.status检测Agent完成工作)session.deleted:会话被删除时触发
注意了:
session.idle钩子不支持async: true,且不能用于阻止会话结束——它只是一个“通知”钩子。
运行验证:配置完成后,启动一个新会话。检查.opencode/session.log中是否出现了“新会话已创建”的记录。完成对话后退出,检查是否出现了“会话完成”的记录。
第六步:高级钩子配置——scope、runIn与async
目标:了解钩子的高级配置选项,精细控制钩子的作用范围和执行方式。
YAML Hooks提供了三个高级配置字段,让你精细控制钩子的行为。
scope:控制钩子触发范围
| 值 | 含义 |
|---|---|
all(默认) | 主会话和子会话都可以触发 |
main | 只有根会话可以触发 |
child | 只有子会话可以触发 |
-event:file.changedscope:main# 只在根会话中触发actions:-bash:"npm run build"# 构建任务只在根会话中运行runIn:控制动作执行位置
| 值 | 含义 |
|---|---|
current(默认) | 在触发钩子的会话中执行 |
main | 在根会话中执行 |
-event:tool.after.editrunIn:main# 在根会话中执行actions:-bash:"git add . && git commit -m 'auto: format'"async:异步执行
当async: true时,钩子立即返回,动作在后台异步执行。适合不阻塞主流程的任务。
-event:file.changedasync:true# 异步执行,不阻塞actions:-bash:"npm run lint"# linting在后台运行注意了:
async: true不能用于tool.before.*和session.idle钩子。异步钩子只能使用bash动作。
运行验证:配置一个带scope: main和async: true的钩子,在子会话中触发它,观察动作是否在根会话中异步执行。
第七步:综合实战——构建完整的自动化工作流
目标:把前面学到的所有钩子组合起来,构建一个“编码 → 格式化 → 测试 → 审计”的完整自动化流水线。
现在我们把所有技能整合到一个完整的hooks.yaml中:
# .opencode/hook/hooks.yaml# 完整的自动化工作流配置hooks:# ========== 文件变更自动化 ==========# 1. 代码文件修改后自动格式化-id:auto-format# 可选ID,用于后续覆盖event:file.changedconditions:-matchesCodeFilesactions:-bash:"npx prettier --write {{ .Paths }}"# 2. 测试文件修改后自动运行对应测试-event:file.changedconditions:-matchesAnyPath:"**/*.test.ts"-matchesAnyPath:"**/*.spec.ts"actions:-bash:"npm test -- --findRelatedTests {{ .Paths }}"# 3. 文档文件修改后自动更新索引-event:file.changedconditions:-matchesAnyPath:"docs/**/*.md"async:true# 异步执行,不阻塞actions:-bash:"npm run docs:build"# ========== 工具执行安全 ==========# 4. 禁止读取敏感文件-event:tool.before.readaction:stopconditions:-matchesAnyPath:"**/.env"-matchesAnyPath:"**/.env.*"-matchesAnyPath:"**/secrets.json"actions:-bash:|echo "❌ 安全策略:禁止读取敏感文件" exit 2# 5. 危险命令审计-event:tool.before.bashactions:-bash:|echo "[AUDIT] $(date) | 命令: {{ .Command }}" >> .opencode/audit.log# 6. 限制危险命令(仅示例,不实际执行)-event:tool.before.bashaction:stopconditions:-matchesAnyPath:"rm -rf /"# 匹配危险命令actions:-bash:|echo "❌ 安全策略:禁止执行危险命令" exit 2# ========== 会话生命周期 ==========# 7. 会话创建时记录-event:session.createdscope:mainactions:-bash:|echo "[SESSION] 创建: $(date)" >> .opencode/session.log# 8. 会话完成时自动总结和提交-event:session.idlescope:mainrunIn:mainactions:-bash:|echo "[SESSION] 完成: $(date)" >> .opencode/session.log # 如果有未提交的改动,自动提交 if [ -n "$(git status --porcelain)" ]; then git add . git commit -m "auto: OpenCode session completed at $(date)" fi逐行解释关键配置:
id: auto-format:给钩子一个唯一ID,方便后续在另一个配置文件中覆盖或禁用matchesAnyPath:支持glob模式匹配文件路径- 多个
actions按顺序执行,任意一个失败会中断后续动作 scope: main+runIn: main:确保提交操作只在根会话中执行一次
运行验证:完成配置后,在一个真实项目中正常使用OpenCode完成一次编码任务。观察:
- 修改代码文件后,Prettier是否自动运行
- 如果修改了测试文件,对应的测试是否自动运行
- 尝试让AI读取
.env文件,是否被阻止 - 会话结束后,
.opencode/session.log中是否有记录 - 如果有未提交的改动,是否被自动提交
异常处理与常见坑
报错1:YAML格式错误导致钩子不生效
(配置了hooks.yaml但没有任何钩子被触发)原因:YAML文件格式错误——缩进不对、缺少必要字段、或者字段名拼写错误。
解决方案:
- 用YAML验证工具检查格式(如
yamllint hooks.yaml) - 确认
hooks是数组(以-开头) - 确认每个钩子都有
event字段 - 确认每个钩子都有非空的
actions数组 - 重启OpenCode后查看启动日志是否有解析错误
报错2:action: stop不生效,危险操作仍然执行
(配置了tool.before.read + action: stop,但AI仍然读取了敏感文件)原因:action: stop需要bash脚本以exit 2退出才能触发阻止逻辑。
解决方案:
- 确认bash脚本中使用了
exit 2:actions:-bash:|echo "阻止执行" exit 2 # 必须用 exit 2 - 确认钩子是
tool.before.*类型(action: stop只支持pre-tool钩子) - 检查是否有其他钩子覆盖了该规则
- 查看OpenCode日志确认钩子是否被触发
报错3:file.changed钩子在文件修改后没有触发
(修改了文件,但file.changed钩子没有执行)原因:file.changed只对通过OpenCode工具(如edit、write)修改的文件生效,对你在IDE中手动修改的文件不触发。
解决方案:
- 确认文件修改是通过OpenCode的
edit或write工具完成的 - 如果需要在IDE中手动修改后也触发,考虑使用文件系统监听工具(如
watchman)配合外部脚本 - 检查
conditions是否过滤掉了你的文件——用matchesAnyPath: "**/*"测试是否所有文件都能触发 - 确认
hooks.yaml文件路径正确:<项目>/.opencode/hook/hooks.yaml
本章产出总结
完成本篇后,你获得了以下能力/产出:
| 序号 | 产出物/能力 | 说明 |
|---|---|---|
| 1 | 理解事件钩子系统 | 知道OpenCode的事件驱动架构和33+种事件类型 |
| 2 | YAML Hooks安装 | 安装了opencode-yaml-hooks插件 |
| 3 | 文件变更自动化 | 配置了file.changed钩子自动格式化代码 |
| 4 | 安全门禁 | 配置了tool.before.*钩子阻止读取敏感文件 |
| 5 | 会话生命周期自动化 | 配置了session.created和session.idle钩子 |
| 6 | 高级钩子配置 | 掌握了scope、runIn、async的用法 |
| 7 | 完整自动化流水线 | 构建了“编码→格式化→测试→审计”的完整工作流 |
事件钩子让OpenCode从“你指挥它干活”变成了“它自己找活干”。从今天开始,你的每一次代码修改都会自动触发格式化、测试、审计——你只需要专注于写代码,剩下的重复工作交给OpenCode的钩子系统。
作者互动与资源引导
你在配置自动化钩子的过程中有没有遇到什么特别的需求?或者你写了什么好用的钩子规则想跟大家分享?欢迎在评论区留言,我看到就会回复——自动化工作流的可能性是无限的,每个人的场景都不一样,期待看到你的创意。
如果觉得这个专栏对你有帮助:
- 关注我,后续每一篇更新你都不会错过
- 关注后私信我,发送暗号“爱学Python”,我会把Python全栈学习路线图和本专栏的源码包发给你
我们还有一个技术交流群,群里的小伙伴们每天都在讨论OpenCode的各种自动化玩法。想进群的朋友在评论区扣个“1”,我拉你进来。
下篇预告
下一篇是[[项目篇19] 初始化OpenCode智能问答机器人项目结构],我们会进入专栏的项目实战篇——从零开始搭建一个基于OpenCode的智能问答机器人,把前面学到的所有知识(插件开发、向量记忆、多模型路由、事件钩子)整合到一个真实项目中。
如果本篇对你有帮助,点赞、收藏、关注走一波,咱们下篇见!