核心功能

Hooks

Hook 用来在特定事件时机自动执行动作——在会话开始时注入团队约束、在工具调用前做安全检查、在模型准备结束时校验产出。适合希望在会话、模型请求、工具调用和停止阶段自动执行检查、补充上下文或实施策略的插件开发者与项目维护者。

Hook 本质上是一个本地子进程协议:ZCode 向进程的 stdin 写入一行 JSON,进程通过退出码和 stdout JSON 返回结果。Hook 不会获得可直接调用 ZCode 模型的内部对象。

安全提示:Hook 会执行本地代码。启用第三方插件前请审查其来源、hooks/hooks.json 和脚本。


执行顺序与事件

新 session → SessionStart
用户提交 → UserPromptSubmit → 主模型
主模型请求工具 → PreToolUse → 需要确认时 PermissionRequest → 执行工具
工具成功 → PostToolUse;工具失败 → PostToolUseFailure
主模型准备结束 → Stop → 结束,或注入反馈后继续主模型
事件matcher主要用途与效果
SessionStart匹配 source,常见值为 startup / clear / compact首轮模型请求前初始化环境、注入项目约束或操作说明
UserPromptSubmit不参与过滤,即使填写也会执行模型调用前补充上下文,或阻断本次用户请求;不能改写原始 prompt
PreToolUse匹配工具名允许、询问或拒绝工具;可完整替换工具输入,替换后会重新校验 schema
PermissionRequest匹配工具名只在权限结果需要询问时触发;可允许、拒绝、更新输入或权限规则
PostToolUse匹配工具名工具成功后追加模型可见上下文;不能替换工具输出
PostToolUseFailure匹配工具名工具失败后追加恢复建议、诊断或重试约束
Stop不参与过滤,即使填写也会执行模型准备结束时检查结果;返回 block 可让现有主模型循环继续,最多连续 3 次

配置来源

来源适用场景生效方式
~/.zcode/cli/config.json当前用户的所有工作区必须在该文件中设置 hooks.enabled: true
<workspace>/.zcode/config.json随项目版本管理的团队规则必须在该文件中设置 hooks.enabled: true
插件 hooks/hooks.json随插件安装和分发标准位置自动发现,随插件启停;不需要再在 manifest 中重复声明同一文件
.agents/settings.json / .claude/settings.json迁移旧配置只读展示,不直接执行;需在设置页显式导入到 .zcode

执行顺序是 user Hook → workspace Hook → 已启用插件 Hook。同一来源内按数组顺序执行。user 与 workspace 配置是拼接关系,不是项目配置覆盖用户配置。

每个 session 启动时会捕获一份 Hook 配置快照。修改文件、在设置页保存或启停插件后,请新建 session 验证;已经启动的 session 不保证热更新。

用户 / 工作区配置示例:

{
  "hooks": {
    "enabled": true,
    "timeoutMs": 60000,
    "maxOutputBytes": 32768,
    "events": {
      "PreToolUse": [
        {
          "matcher": "Write|Edit",
          "hooks": [
            {
              "type": "process",
              "command": "node",
              "args": ["scripts/check-write.mjs"],
              "enabled": true,
              "timeoutMs": 10000
            }
          ]
        }
      ]
    }
  }
}

快速上手:第一个插件 Hook

下面的插件在新 session 启动时给模型补充一条团队约束。示例依赖系统 PATH 中可用的 node

context-guard/
├── .zcode-plugin/
│   └── plugin.json
└── hooks/
    ├── hooks.json
    └── context.mjs

plugin.jsonhooks/hooks.json 是标准位置会自动加载,manifest 不必再写 hooks 字段):

{
  "name": "context-guard",
  "version": "0.1.0",
  "description": "在会话开始时注入团队开发约束"
}

hooks/hooks.json

{
  "description": "Context Guard hooks",
  "hooks": {
    "SessionStart": [
      {
        "matcher": "startup|clear|compact",
        "hooks": [
          {
            "type": "process",
            "command": "node",
            "args": ["${ZCODE_PLUGIN_ROOT}/hooks/context.mjs"],
            "timeoutMs": 5000
          }
        ]
      }
    ]
  }
}

hooks/context.mjs

let raw = "";
process.stdin.setEncoding("utf8");
for await (const chunk of process.stdin) raw += chunk;

const input = JSON.parse(raw);
process.stderr.write("[context-guard] " + input.hook_event_name + "\n");
process.stdout.write(JSON.stringify({
  hookSpecificOutput: {
    hookEventName: input.hook_event_name,
    additionalContext: "本项目提交前必须运行类型检查和受影响测试。"
  }
}));

验证步骤:

  1. 设置 -> 插件管理 -> Marketplace 中添加本地市场或插件来源,安装并启用插件。
  2. 设置 -> Hooks 确认插件 Hook 以只读条目出现,事件、matcher、命令和来源路径正确。
  3. 新建 session 并发起请求;首轮模型应能看到注入的团队约束。
  4. 调试时把日志写到 stderr,stdout 只输出协议结果,避免诊断文字破坏 JSON。

执行器、超时与 matcher

类型语义建议
processcommand + args[],直接按 argv 执行,不经过 shell;只支持同步参数边界清楚,跨平台更稳定,优先用于 Node、Python 或二进制脚本
command把完整字符串交给系统 shell;可设置 shellasynctimeout适合兼容现有 Claude Marketplace 插件;注意 Windows、macOS、Linux 的 shell 和引用差异
  • timeoutMs 单位是毫秒;兼容字段 timeout 单位是秒。两者同时存在时优先 timeoutMs
  • 根级默认超时是 60000 ms,默认 stdout 上限是 32768 bytes。
  • 单条 Hook 可写 enabled: false;runtime 会真正跳过,不只是界面置灰。
  • commandasync: true 是 fire-and-forget:当前事件立即继续,后台 stdout 不能阻断、改输入或注入上下文;超时、取消、完成和失败仍记录生命周期。
  • statusMessage 当前会保存并在设置页展示,但还不是运行时的实时状态提示。

matcher 规则

  • 缺省、空字符串或 *:匹配全部。
  • 只含字母、数字、下划线和 |:按精确名称列表匹配,例如 Write|Edit
  • 包含其他字符:按 JavaScript 正则处理;非法正则不执行该 matcher,并产生诊断。
  • 工具事件匹配实际工具名,并兼容 Agent / Task alias。
  • SessionStart 匹配 sourceUserPromptSubmitStop 不使用 matcher 过滤。

stdin 输入契约

ZCode 向每个 Hook 写入「一行 JSON + 换行」。同一份输入同时保留 ZCode camelCase 字段与 Claude Code snake_case alias,旧插件可以继续读取 snake_case。

{
  "session_id": "session-123",
  "transcript_path": "/tmp/zcode-hook/transcript.jsonl",
  "cwd": "/workspace/demo",
  "permission_mode": "default",
  "hook_event_name": "PreToolUse",
  "tool_name": "Write",
  "tool_input": {
    "file_path": "src/index.ts",
    "content": "..."
  },
  "tool_use_id": "tool-123"
}
事件重点字段
SessionStartsource,可选 agent_typemodel
UserPromptSubmitprompt
PreToolUsetool_nametool_inputtool_use_id
PermissionRequesttool_nametool_input,有真实数据时包含 permission_suggestions
PostToolUse完整结构化 tool_response,以及工具名、输入和调用 ID
PostToolUseFailure字符串 erroris_interrupt,以及工具字段
Stopstop_hook_activelast_assistant_message

transcript_path 指向本次 Hook 可读的临时 JSONL 文件。ZCode 会在 Hook 完成后清理临时目录,不要把它当作长期存储;插件持久化数据请写入 ZCODE_PLUGIN_DATA


stdout、退出码与常用返回值

stdout 为空表示成功且无附加效果;非 JSON stdout 只作为诊断,不进入模型上下文。只有去除前导空白后以 { 开头的合法 JSON 才按协议解析。未知字段会忽略,已知字段类型错误或事件名不符会让当前 Hook 可恢复失败,不影响后续 Hook。

注入上下文(推荐使用 hookSpecificOutput,事件归属最清楚;也兼容顶层 additionalContext / additional_context):

{
  "hookSpecificOutput": {
    "hookEventName": "SessionStart",
    "additionalContext": "只修改与当前任务相关的文件。"
  }
}

PreToolUse:修改或拒绝工具调用

{
  "hookSpecificOutput": {
    "hookEventName": "PreToolUse",
    "permissionDecision": "allow",
    "permissionDecisionReason": "已重定向到允许目录",
    "updatedInput": {
      "file_path": "generated/index.ts",
      "content": "..."
    },
    "additionalContext": "文件已被重定向到 generated 目录。"
  }
}

updatedInput 是完整替代对象,不是局部 patch;ZCode 会用工具 schema 重新校验。拒绝时返回 permissionDecision: "deny"permissionDecisionReason。多个 Hook 聚合时,deny 优先于 ask,ask 优先于 allow。

PermissionRequest:自动允许或拒绝权限询问

{
  "hookSpecificOutput": {
    "hookEventName": "PermissionRequest",
    "decision": {
      "behavior": "deny",
      "message": "生产目录只能在发布流程中修改"
    }
  }
}

允许时把 behavior 改为 allow,并可在 decision 中返回 updatedInputupdatedPermissions;历史字段 permissionUpdates 也兼容。显式 deny 规则、Plan 模式写入禁令和工具硬限制不能被 Hook allow 绕过。

UserPromptSubmit:阻断本次模型请求

{
  "continue": false,
  "reason": "请先提供工单号",
  "hookSpecificOutput": {
    "hookEventName": "UserPromptSubmit",
    "additionalContext": "该请求被团队策略阻断。"
  }
}

Stop:让主模型继续一轮

{
  "decision": "block",
  "reason": "还没有给出测试命令和结果,请补齐后再结束。"
}

decision: "block" 需要带 reason 或 additionalContext 才会续跑。为兼容旧 ZCode 配置,也接受 continue: true 且带 additionalContext。连续续跑达到 3 次后会强制结束,防止无限循环。

退出码

退出码含义
0成功,解析 stdout
2阻断快捷方式;在可阻断事件中产生 block / deny,在 Stop 中产生继续一轮的反馈
其他非零当前 Hook 可恢复失败,记录诊断,turn 不会因此整体崩溃

插件发现、变量与安全边界

  • manifest 查找优先级:.zcode-plugin/plugin.json.claude-plugin/plugin.json
  • 标准 hooks/hooks.json 自动加载;manifest 的 hooks 还支持相对 JSON 路径、inline 对象或二者数组。不要让 manifest 再指向同一个标准文件,否则会记录重复诊断并跳过重复项。
  • 插件进程可读取 ZCODE_PLUGIN_ROOTZCODE_PLUGIN_DATAZCODE_PLUGIN_IDZCODE_PLUGIN_NAME;兼容变量 CLAUDE_PLUGIN_ROOTCLAUDE_PLUGIN_DATA 也会注入。
  • 命令、参数里的插件路径变量会在执行前替换。长期数据写入 ZCODE_PLUGIN_DATA,不要写回安装目录。
  • 插件 Hook 在设置页只读,启停跟随插件本身。

在设置页创建和维护 Hook

  1. 打开 设置 -> Hooks
  2. 选择 user 或 workspace scope,新增事件、执行类型、matcher、命令 / 参数、超时、async 和状态文案。
  3. 已有 .zcode Hook 支持查看、编辑、删除和单条启停。编辑时不能直接改变 scope;需要删除后在目标 scope 重建。

Hook 不生效时先确认:配置来源里 hooks.enabled 是否为 true、插件是否处于启用状态,然后新建一个 session——Hook 配置在 session 启动时形成快照,修改配置或启停插件不会影响已经启动的 session。


下一步