OpenCode Hooks 系统完全指南:让 AI 编程助手随你的工作流起舞

OpenCode Hooks 系统完全指南:让 AI 编程助手随你的工作流起舞

引言

在日常开发中,我们经常会遇到这样的场景:每次 AI 写完代码需要手动运行 lint 检查、想要在会话结束时自动提交代码、或者希望在特定文件变更时触发测试套件。如果你熟悉 Git Hooks 的概念,你就会明白"钩子"在开发工作流中的价值——它让你在特定事件发生时自动执行预设动作,无需手动干预。

OpenCode 作为一款现代化的 AI 编程助手,内置了一套强大且灵活的 Hooks 系统。通过 Hooks,你可以监听 OpenCode 的生命周期事件(会话创建、文件变更、工具调用前后等),并在这些事件发生时自动执行自定义逻辑。本文将深入讲解 OpenCode Hooks 系统的两种实现方式——插件 HooksYAML Hooks,并给出完整的实战示例。

Hooks 系统概览

OpenCode 的 Hooks 系统包含两条路径:

| 方式 | 描述 | 适用场景 |
|------|------|----------|
| 插件 Hooks | 通过 JavaScript/TypeScript 文件实现,使用 export 函数暴露钩子 | 复杂逻辑、需要访问上下文、自定义工具集成 |
| YAML Hooks | 通过 hooks.yaml 声明式配置,配合 opencode-yaml-hooks 插件使用 | 简单脚本执行、自动化工作流、CI 集成 |

两种方式可以同时使用,互不冲突。插件 Hooks 提供更细粒度的控制能力和完整的上下文访问,而 YAML Hooks 胜在简洁直观、易于维护。

插件 Hooks:深度定制

插件的工作机制

在 OpenCode 中,插件就是一个放置在 .opencode/plugins/ 目录下的 JavaScript 或 TypeScript 文件。每个插件文件 export 一个或多个钩子函数,OpenCode 在启动时自动加载它们。钩子函数接收一个 context 对象,返回一个 hooks 对象。

插件加载的优先级如下:

全局配置 (~/.config/opencode/opencode.json) 中的 plugin 字段

项目配置 (opencode.json) 中的 plugin 字段

全局插件目录 (~/.config/opencode/plugins/)

项目插件目录 (.opencode/plugins/)

// .opencode/plugins/my-first-hook.ts
export const guard = {
  name: "env-guard",
  async toolExecuteBefore(context: any) {
    const { toolName, input } = context

    if (toolName === "read" && input?.filePath?.includes(".env")) {
      return { prevented: true, message: "禁止读取环境配置文件" }
    }
  },

  async event(context: any) {
    const { event } = context.input

    if (event.type === "session.created") {
      console.log(`新会话已创建: ${new Date().toISOString()}`)
    }
  },
}

核心钩子一览

OpenCode 插件系统提供了丰富的钩子事件,以下是完整的钩子列表及其用途:

#### 配置与初始化钩子

| 钩子 | 触发时机 | 用途 |
|------|----------|------|
| config | 插件初始化时 | 动态注入命令、Agent、MCP 服务器 |
| tool | 插件初始化时 | 注册自定义工具 |
| auth | 认证需求触发时 | 注册自定义认证提供者 |

#### 消息与对话钩子

| 钩子 | 触发时机 | 可修改 |
|------|----------|--------|
| chat.message | 处理传入消息时 | 消息内容和部件 |
| chat.params | 发送 LLM 请求前 | temperature、topP 等参数 |
| experimental.text.complete | LLM 生成文本后 | 生成的文本内容 |

#### 工具执行钩子

| 钩子 | 触发时机 | 可修改 |
|------|----------|--------|
| tool.execute.before | 工具执行前 | 工具参数、阻止执行 |
| tool.execute.after | 工具执行后 | 工具输出 |

#### 事件监听钩子

event 钩子可以订阅所有系统事件,包括:

  • 会话事件session.createdsession.deletedsession.compactedsession.idle
  • 文件事件file.editedfile.watcher.updated
  • 工具事件tool.execute.beforetool.execute.after
  • 消息事件message.part.removedmessage.part.updatedmessage.removedmessage.updated
  • 权限事件permission.repliedpermission.updated
  • TUI 事件tui.prompt.appendtui.command.executetui.toast.show
  • LSP 事件lsp.client.diagnosticslsp.updated
  • 命令事件command.executed

#### 会话压缩钩子

| 钩子 | 触发时机 | 用途 |
|------|----------|--------|
| experimental.session.compacting | 会话压缩前 | 注入自定义上下文或替换压缩提示词 |

实战案例一:代码质量守卫

创建一个插件,在文件写入后自动运行 ESLint 检查:

// .opencode/plugins/code-quality.ts
import { execSync } from "child_process"

export const qualityGuard = {
  name: "code-quality",

  async toolExecuteAfter(context: any) {
    const { toolName, output } = context

    if (toolName !== "write" && toolName !== "edit") {
      return
    }

    try {
      const result = execSync("npx eslint --fix --quiet .", {
        cwd: process.cwd(),
        timeout: 30000,
      })
      console.log("[qualityGuard] ESLint 检查通过")
    } catch (error: any) {
      console.warn("[qualityGuard] ESLint 发现问题:", error.stdout?.toString())
    }
  },
}

实战案例二:会话活动日志

记录每个会话的关键操作,便于回顾和审计:

// .opencode/plugins/session-logger.ts
import fs from "fs"
import path from "path"

export const sessionLogger = {
  name: "session-logger",

  async event(context: any) {
    const { event } = context.input
    const logFile = path.join(
      process.cwd(),
      ".opencode",
      "session-logs.jsonl"
    )

    const entry = {
      timestamp: new Date().toISOString(),
      type: event.type,
      sessionId: event.session?.id,
      details: event.type.startsWith("tool.") ? event.tool?.name : null,
    }

    fs.appendFileSync(logFile, JSON.stringify(entry) + "\n")
  },
}

YAML Hooks:声明式自动化

如果你不需要编写复杂的 JavaScript 逻辑,YAML Hooks 是一个更轻量的选择。它通过 YAML 配置文件定义钩子行为,使用了一个名为 opencode-yaml-hooks 的社区插件。

安装与配置

首先在 opencode.json 中注册插件:

{
  "$schema": "https://opencode.ai/config.json",
  "plugin": ["opencode-yaml-hooks"]
}

然后在 .opencode/hook/hooks.yaml 中定义钩子规则:

hooks:
  - event: file.changed
    conditions:
      - matchesAnyPath: src/**/*.ts
    actions:
      - bash: "npm run lint -- --fix"

  - event: session.created
    scope: main
    actions:
      - bash: "echo '新会话开始于:' $(date)"

配置文件位置(按加载顺序):

| 平台 | 全局配置 | 项目配置 |
|------|----------|----------|
| macOS / Linux | ~/.config/opencode/hook/hooks.yaml | <project>/.opencode/hook/hooks.yaml |
| Windows | ~/.config/opencode/hook/hooks.yaml 优先,否则 %APPDATA%/opencode/hook/hooks.yaml | <project>/.opencode/hook/hooks.yaml |

YAML Hooks 核心字段详解

hooks:
  - id: my-hook-id          # 可选:用于后续覆盖或禁用
    event: <事件名>          # 必填:触发事件
    scope: all|main|child   # 可选:作用域,默认 all
    runIn: current|main     # 可选:执行位置,默认 current
    async: true|false        # 可选:是否异步执行
    action: stop             # 可选:仅 tool.before.* 可用,阻止工具执行
    conditions:              # 可选:条件过滤
      - matchesCodeFiles
      - matchesAnyPath: src/**/*.ts
    actions:                 # 必填:执行动作数组
      - bash: "npm test"

#### 支持的事件类型

| 事件 | 触发时机 | 说明 |
|------|----------|------|
| session.created | 会话创建时 | 适合初始化操作 |
| session.deleted | 会话删除时 | 适合清理操作 |
| session.idle | 会话空闲时 | 适合自动提交等操作 |
| file.changed | 文件被修改后 | 最常用,适合 lint、format |
| tool.before.* | 任何工具执行前 | 适合安全检查、权限控制 |
| tool.before.<toolname> | 特定工具执行前 | 如 tool.before.write |
| tool.after.* | 任何工具执行后 | 适合日志记录 |
| tool.after.<toolname> | 特定工具执行后 | 如 tool.after.edit |

#### 动作类型

YAML Hooks 支持三种动作类型:

# 1. Bash 命令(最常用)
actions:
  - bash: "npm run lint"
  - bash:
      command: "$OPENCODE_PROJECT_DIR/.opencode/hooks/init.sh"
      timeout: 30000  # 毫秒,默认 60000

# 2. OpenCode 命令
actions:
  - command: "/share"

# 3. 工具调用
actions:
  - tool:
      name: write
      args:
        filePath: "hook-output.txt"
        content: "钩子已触发"

#### 条件过滤系统

# 仅在修改了代码文件时触发
conditions:
  - matchesCodeFiles

# 仅在匹配特定路径时触发
conditions:
  - matchesAnyPath: src/**/*.ts

# 要求所有文件都匹配指定路径
conditions:
  - matchesAllPaths:
      - package.json
      - apps/*/package.json

多个条件之间是"与"关系,必须全部满足才会执行。

实战案例三:提交前自动格式化

# .opencode/hook/hooks.yaml
hooks:
  - id: auto-format
    event: file.changed
    conditions:
      - matchesAnyPath:
          - src/**/*.ts
          - src/**/*.tsx
          - src/**/*.js
    actions:
      - bash: "npx prettier --write src/**/*.{ts,tsx,js}"
      - bash: "npx eslint --fix src/**/*.{ts,tsx,js}"

实战案例四:文件写入安全检查

hooks:
  - id: block-env-write
    event: tool.before.write
    conditions:
      - matchesAnyPath:
          - .env
          - .env.local
          - .env.production
          - **/.env
    action: stop
    actions:
      - bash: "echo '❌ 禁止覆盖环境文件!'"

action: stop 会让 OpenCode 尝试中止当前会话,有效阻止危险操作。

实战案例五:空闲时自动测试

hooks:
  - id: auto-test-on-idle
    event: session.idle
    scope: main
    conditions:
      - matchesCodeFiles
    async: true
    actions:
      - bash:
          command: "npm test -- --changed"
          timeout: 120000

async: true 表示异步执行,不会阻塞 OpenCode 的正常工作流。注意异步钩子只能使用 bash 动作。

钩子执行顺序

了解钩子的触发顺序对于正确设计工作流至关重要。以 write 工具为例,完整的触发链路如下:

1. tool.before.*       (所有工具之前)
2. tool.before.write   (特定工具之前)
3. 工具执行
4. file.changed        (检测到文件变更)
5. tool.after.*        (所有工具之后)
6. tool.after.write    (特定工具之后)

全局钩子先于项目钩子加载,同一事件内的钩子按声明顺序执行。

钩子覆盖与禁用

YAML Hooks 支持通过 id 字段进行覆盖操作。这在你想在项目级配置中修改全局钩子行为时特别有用:

# 全局配置中定义
hooks:
  - id: gbl-auto-lint
    event: file.changed
    actions:
      - bash: "npm run lint"

# 项目配置中覆盖
hooks:
  - id: gbl-auto-lint
    override:
      event: file.changed
      actions:
        - bash: "npm run lint:strict"

# 项目中禁用
hooks:
  - id: gbl-auto-lint
    disable: true

覆盖规则:

  • 项目钩子可以覆盖全局钩子
  • 全局钩子不能覆盖项目钩子
  • 同一文件内的覆盖不起作用

最佳实践

1. 优先使用 file.changed

对于文件相关的自动化操作(如 lint、format、测试),优先使用 file.changed 事件而不是 tool.after.*file.changed 在文件实际变更后才触发,语义更清晰。

# 推荐
- event: file.changed
  conditions:
    - matchesAnyPath: src/**/*.ts
  actions:
    - bash: "npm run lint"

# 不推荐 —— 语义不够精确
- event: tool.after.*
  conditions:
    - matchesAnyPath: src/**/*.ts
  actions:
    - bash: "npm run lint"

2. 异步执行耗时操作

对于可能耗时较长的操作(如运行完整测试套件),使用 async: true 避免阻塞交互:

- event: session.idle
  scope: main
  async: true
  actions:
    - bash:
        command: "npm run test:e2e"
        timeout: 300000

3. 善用 scope 限制触发范围

scope 可选值为 all(默认)、mainchildmain 限制仅在主会话触发,child 限制仅在子 Agent 会话触发。对于全局性操作(如自动 commit),建议使用 scope: main

4. 插件钩子保持轻量

插件钩子中的同步或长时间运行的操作会阻塞 OpenCode 的正常工作。对于重操作,考虑:

  • 使用子进程异步执行
  • 将逻辑拆分为独立的脚本文件
  • 利用事件日志器 + 外部监听器模式

5. 组合使用两种方式

复杂项目建议组合使用两种方式:

  • 用 YAML Hooks 处理标准化的自动化任务
  • 用插件 Hooks 实现需要访问完整上下文的定制逻辑

总结

OpenCode 的 Hooks 系统为你提供了对 AI 编程助手行为前所未有的控制力。通过合理利用 Hooks,你能够构建一个高度自动化的开发环境:

  • 插件 Hooks 适合需要复杂逻辑和上下文访问的场景,几乎可以拦截和修改 OpenCode 的所有行为
  • YAML Hooks 适合简单的声明式自动化,学习成本低、维护方便
  • 两者结合可以覆盖从代码质量检查、安全防护到自动化测试在内的完整工作流

用好 Hooks 的关键在于理解事件的触发时机和执行顺序,选择合适的钩子类型,并在全局和项目级别之间做好平衡。当你的 OpenCode 开始自动运行 lint 检查、阻止危险操作、在会话空闲时触发测试时,你会发现:好的工具不是替你思考,而是帮你省下重复思考的时间