文档

钩子

钩子是 Mework 在一轮对话的固定时刻运行的命令:你发出消息时、每次工具调用前后、模型准备结束时。用它可以检查、拦截或改写将要发生的事,也可以给模型补充上下文。格式沿用 Claude Code 的 hooks 块,从 Claude Code 拷来的配置不用改就能用。

hooks.json 放在哪里

层级 文件 谁会用到
全局 ~/.mework/hooks.json(Windows:%USERPROFILE%\.mework\hooks.json) 所有对话
工作区 <工作区>/.mework/hooks.json 包含这个工作区的对话

对话可以从全局钩子和它所有工作区的钩子里挑选。全局钩子在本机运行。工作区在 WSL 或 SSH 机器上时,Mework 在那台机器上读取工作区的 hooks.json,其中的钩子也在那台机器上运行。对话在某个工作区的隔离工作树里工作时,这个工作区的 hooks.json 仍从它自己的文件夹读取。临时项目的临时文件夹没有 .mework;那里的对话能用全局钩子,以及它附加的工作区的钩子。旧的 .naiword/hooks.json 只在同一层级没有 .mework/hooks.json 时才会被读取。

文件格式

<工作区>/.mework/hooks.json:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "^(bash|zsh|sh|powershell)$",
        "hooks": [
          {
            "type": "command",
            "name": "No force push",
            "command": "python3 .mework/hooks/no_force_push.py",
            "commandWindows": "python .mework/hooks/no_force_push.py",
            "timeout": 10
          }
        ]
      }
    ]
  }
}

<工作区>/.mework/hooks/no_force_push.py:

import json, sys

call = json.load(sys.stdin)
if "push --force" in call["tool_input"].get("command", ""):
    print("Force pushes are not allowed in this project.", file=sys.stderr)
    sys.exit(2)

hooks 下每个事件对应一组列表;每组有一个可选的 matcher 和一组处理程序。文件不存在、不是合法 JSON、顶层没有 hooks 对象时,就没有任何钩子。文件开头带 UTF-8 字节顺序标记(BOM)没有关系。

事件

事件名区分大小写;其他名字会被跳过。

事件 什么时候触发 matcher 匹配的对象
SessionStart Mework 启动后对话的第一轮,所以重启后会再触发一次 startup
UserPromptSubmit 每一轮,在第一次请求模型之前 (忽略)
PreToolUse 每次工具调用之前,先于审批判断 工具名
PermissionRequest 调用将要弹出审批卡,或者 PreToolUse 钩子要求弹卡时 工具名
PostToolUse 工具运行之后;运行前就被拒绝的调用不触发 工具名
Stop 模型不再调用工具、准备结束这一轮时 (忽略)
InstructionsLoaded 加载 MEWORK.md、规则文件或导入的文件时;退出码和输出都不改变任何事 加载原因

子代理和工作流步骤会运行 PreToolUse、PermissionRequest、PostToolUse 和 InstructionsLoaded 钩子。SessionStart、UserPromptSubmit 和 Stop 只在对话自己的回合里运行。

matcher

  • matcher 是正则表达式,而且不锚定:edit 也会匹配 edit_global_memory。只想匹配一个工具时写 ^edit$。
  • 不写、写空字符串或写 "*" 都匹配全部。正则写错时,整组被丢弃。
  • Claude Code 的工具名(Bash、Read、Write|Edit 等)会匹配 Mework 里做同样事情的工具;Mework 自己的名字(bash、write、edit 等,见内置工具)也可以用。
  • 要匹配某个 MCP 服务器的全部工具,用 ^mcp__<服务器名>_。

处理程序的键

键 作用 默认值
type 必须是 "command"。其他类型或不写的处理程序会被跳过。 —
command 必填。要运行的命令。在本机时,macOS 上用 bash -lc <command>;Windows 上用 pwsh,没有时用 Windows PowerShell,输出固定为 UTF-8。在 WSL 或 SSH 机器上,用哪种 shell 由那台机器的系统决定。 —
commandWindows 钩子在 Windows 机器上运行时替代 command。 —
name 列表和时间线卡片上显示的名字。 statusMessage,再没有就是 <事件> #<n>
statusMessage 钩子运行时卡片上显示的文字。 「正在执行」
timeout 秒数,1–600。超出范围时这个处理程序被移除。 30
async 只有 InstructionsLoaded 允许设为 true;其他事件上设为 true 会移除这个处理程序,asyncRewake: true 也一样。 —

为对话选择钩子

  1. 打开 更多选项 → 对话设置 → 钩子(见对话设置)。
  2. 勾选这个对话要运行的处理程序。每行是一个处理程序,说明里写着它的事件和 matcher,例如「工具执行前 · matcher ^bash$」。

保存 hooks.json 后,列表很快会自动刷新;按 重新扫描 可以立即刷新,运行时总是按磁盘上的文件为准。列表先是 全局 一节,再按工作区各分一节(见对话设置)。每节标题上的文件夹按钮(打开全局配置目录 或 打开 {path} 的配置目录)会显示这一层的 hooks.json,需要时先创建 .mework 文件夹;对 WSL 或 SSH 工作区,它会在文件面板里打开那台机器上的文件夹。内置的 mework 预设不选任何钩子。

已选的钩子会列在系统提示词里,所以模型的提示缓存未过期时,勾选或取消勾选钩子会标成橙色,并在生效前询问一次。

注意 选择总是指向你勾选的那个处理程序。在 hooks.json 里插入、删除或调整处理程序的顺序后,处理程序挪了位置或被删掉的选择会显示为 悬空,在取消勾选之前每次运行都会失败;取消后再勾选处理程序的新行。

每轮开始前的确认

在 手动 和 允许编辑 两个安全级别下,选了钩子的对话每轮开始前都会弹出对话框「确认本轮生命周期钩子」,列出每个钩子的事件、名称、matcher、命令,以及它在哪个文件夹运行(WSL 或 SSH 工作区的钩子还写明机器)。自己选了钩子的代理角色,它的钩子也列在里面,标着角色名;子代理只运行对话框里列过的钩子。点 允许本轮 继续,点 取消 结束这一轮。列出的内容超过 12,000 个字符时,这一轮会被拒绝,请少选一些钩子。完全访问 下不弹出。

钩子怎样运行

  • 在哪里: 工作区的钩子在这个工作区所在的机器上运行,以工作区文件夹(或对话在这个工作区的工作树)为工作目录。全局钩子在本机运行,以工作区 1 的文件夹为工作目录;但工作区 1 在 WSL 或 SSH 机器上、或者是临时项目的文件夹时,全局钩子改在 Mework 数据目录下的一个临时文件夹里运行。钩子输入里的 cwd 写明钩子在哪个文件夹里运行。
  • 环境: 在本机运行的钩子用 Mework 的完整环境,在 WSL 或 SSH 机器上运行的钩子用那台机器自己的环境,都再加上钩子所属工作区的 环境变量(见工作区),另有值为事件名的 MEWORK_HOOK_EVENT,以及值为钩子所在文件夹的 CLAUDE_PROJECT_DIR。
  • 时间: 同一事件匹配到的处理程序并行运行,各自受 timeout 限制。同一轮里所有钩子共用 5 分钟的总预算,用完后,后续钩子失败。

哪些钩子会看到一次调用

全局钩子什么都看得到。工作区的钩子在以下时候运行:

  • 在这个工作区里工作的工具调用:workspace 参数指向它的工具,例如 read、write、edit、grep、find、ls、lsp、各个 shell 和 preview_start(调用没写 workspace 时算工作区 1);指向它的项目记忆工具;这个工作区自己的 MCP 服务器;以及在它里面运行的全局 "workspace": true 服务器实例(见 MCP 服务器);
  • 对话自己的事件:SessionStart、UserPromptSubmit、Stop,以及不在某一个工作区里工作的工具调用,例如 web_search、web_fetch、agent_spawn、只用 id 指定的预览服务器,以及没有 "workspace": true 的全局 MCP 服务器。

工作区的钩子从不为别的工作区的工具调用运行。InstructionsLoaded 只送到全局钩子和工作区 1 的钩子,因为指令文件属于工作区 1。

stdin 输入

每个钩子都会从 stdin 收到一个 UTF-8 编码的 JSON 对象,包含 session_id(对话)、transcript_path(总是 null)、cwd、hook_event_name、model、permission_mode 和 turn_id。permission_mode 在手动下是 default,允许编辑下是 acceptEdits,完全访问下是 bypassPermissions。各事件另外附带:

事件 附加字段
SessionStart source:总是 "startup"
UserPromptSubmit prompt:你最新一条消息的文字
PreToolUse tool_name、tool_use_id、tool_input
PermissionRequest tool_name、tool_input、permission_suggestions(总是空的)
PostToolUse tool_name、tool_use_id、tool_input(改写之后的)、tool_response
Stop stop_hook_active、last_assistant_message
InstructionsLoaded file_path、memory_type、load_reason,有时还有 globs、trigger_file_path、parent_file_path;没有 transcript_path

tool_input 和 tool_response 用的是 Claude Code 脚本读取的字段名,例如 file_path、old_string、new_string 和 command。

钩子怎样回应

退出码

  • 2:拦截,原因取 stderr。PreToolUse 和 PermissionRequest 拒绝这次调用;UserPromptSubmit 和 SessionStart 在请求模型之前结束这一轮;PostToolUse 拒收结果;Stop 让模型继续干活。
  • 其他非零退出码算作钩子失败,不做任何决定,这一轮照常进行。
  • 0:读取 stdout。为空则什么都不做;是 JSON 对象则按下表处理;是纯文本时,对 SessionStart 和 UserPromptSubmit 作为补充上下文,对 Stop 算作错误,对其他事件忽略。

JSON 输出

字段 效果
continue: false 结束这一轮,原因取 reason,没有时取 stopReason。
decision: "block" 加 reason 拦截,效果同退出码 2。用在 Stop 上时让模型继续:reason 作为一条新的用户消息发出,连续最多 3 次。
systemMessage 显示在钩子的时间线卡片上,模型看不到。
hookSpecificOutput.hookEventName 必须等于当前事件名,否则整个 hookSpecificOutput 都被忽略。
hookSpecificOutput.additionalContext 给模型的文字,在当前这批工具结果之后以 送达了钩子补充的上下文 卡片送达;如果这一轮先结束了,就在下一轮送达。
hookSpecificOutput.permissionDecision(PreToolUse) allow:这次调用不弹审批卡。ask:即使在完全访问下、或之前选过 总是允许,也会弹卡,卡上写明是哪个钩子要求确认。deny:拒绝这次调用,原因取 permissionDecisionReason。
hookSpecificOutput.updatedInput(PreToolUse) 配合 allow 或 ask,在审批判断之前替换调用参数。
hookSpecificOutput.decision(PermissionRequest) {"behavior": "allow", "updatedInput": {…}} 只批准这一次调用。{"behavior": "deny", "message": "…", "interrupt": true} 拒绝调用,interrupt 还会结束这一轮。
顶层的 decision: "approve" 或 "block"(PreToolUse) 旧写法,只在没有 hookSpecificOutput 时生效。

多个钩子回应同一次调用时:任何一个拦截或拒绝都会生效;任何一个 ask 都会弹卡;只有所有返回 allow 或 ask 的钩子给出相同的 updatedInput 时,allow 才算数。

PostToolUse 拦截不会撤销调用:模型会被告知工具已经执行完毕,效果仍然保留。

钩子跳不过的确认

以下确认任何钩子都免除不了:每次都必须询问的 MCP 工具(见 MCP 服务器)、危险的递归删除、写入全局记忆,以及在你已登录的浏览器页面上操作。

查看运行记录

除 InstructionsLoaded 外,每次钩子运行都会在时间线上留下一张卡片,展开可以看到 stdout、stderr、systemMessage 和决定;给模型补充了上下文的钩子带有 已加入模型上下文 标记。这些卡片只留在你的电脑上。更多选项 → 历史记录 也会把每次钩子运行与请求、工具调用列在一起。

在 GitHub 上编辑本页