Hooks · 在生命周期事件上挂确定性逻辑
Hooks 让你在「调工具前/后、会话开始/结束」这些生命周期点上挂脚本或 HTTP 回调——用确定性代码补模型「只靠自觉」的不足。这一章看事件、两种 handler、各事件能拦什么(工具调用、用户 prompt、停止)、fail-open 取向,以及那些安全设计。
Hooks · 在生命周期事件上挂确定性逻辑
技能靠模型「自觉遵守」,但有些事你想要确定性强制——每次编辑后自动格式化、危险命令一律拦。这就是 hooks(xai-grok-hooks):在生命周期事件上挂脚本或 HTTP 回调。
事件与两种 handler
HookEventName 覆盖 SessionStart、UserPromptSubmit、PreToolUse、PostToolUse、Stop、SubagentStart/Stop、PreCompact/PostCompact、SessionEnd 等 16 个名字(含旧别名 SubagentEnd),还有一轮没做完就收场(中断、拒绝权限、到达回合上限)时代替 Stop 触发的 StopCancelled。Claude Code 的 PascalCase 事件名本就是 grok 的原生名,~/.claude/settings.json 里的 hooks 也直接读;此外还认 Cursor 那套 camelCase、按操作细分的事件名(beforeShellExecution→PreToolUse、afterFileEdit→PostToolUse、beforeSubmitPrompt→UserPromptSubmit)。
每个 hook 是两种之一:
- 命令:spawn 一个进程,把事件信封(JSON)写进它 stdin,读它的 exit code + stdout(以及 stderr)判定;
- HTTP 回调:POST 信封到一个 URL,读响应判定。
除了 hooks 目录里的 JSON 文件,hook 也能直接写进 config.toml 的 [[hooks.<事件>]]。
能拦什么:每个事件一种 gate
拦截能力按事件区分:每个事件带一个 GateKind,决定它能管到哪一步。
PreToolUse:工具真正执行前的最后一道闸。判定是Allow/Ask/Defer/Deny四种:Deny把这次调用拦下来(附理由给模型,让它调整),第一个Deny短路;Ask强制弹权限确认;hook 还能用updatedInput改写工具入参(改完仍要过工具 schema 校验,不过就拦),或附上additionalContext。多个 hook 的结论按 Deny > Ask > Defer > Allow 汇总。UserPromptSubmit:能把用户提交的 prompt 直接拦下(退出码 2 或decision: "block")。Stop/SubagentStop:能拦下「停止」,让 agent 接着干活,每轮最多续 8 次。PostToolUse:工具已经跑完,拦不住,但能把理由和补充上下文递给模型,甚至替换模型看到的那份输出。
其余事件才是纯通知。
一个关键取向:fail-open——hook 本身失败(脚本崩了、超时)不阻断工具,源码里写着「出错的 hook 什么也不贡献,只有健康的 deny 才能拦」。威胁模型是「受保护环境里的自动化」,一个坏 hook 不该把 agent 卡死。(对比 MCP 的非交互 OAuth 是 fail-closed——按威胁模型逐处选择,是这套代码反复出现的成熟判断。)
匹配:又是 Claude 兼容
hook 用 matcher 决定对哪些工具生效。HookMatcher 分精确(简单的 Bash|Edit 形式,还会展开出 grok 别名)和正则两种。妙在它接 claude_alias.rs:你写 ^Bash$,它也能匹配 grok 的 run_terminal_command——你的 Claude hook 配置直接能用。matcher 也能用在非工具事件上(按通知类型、subagent 类型、SessionStart 的来源等过滤),Stop 和 UserPromptSubmit 则忽略它。
一串安全设计
hooks 跑外部代码,安全是重点,几处值得学:
- 环境变量顺序即安全:spawn 时先灌用户的
extra_env,再灌 runner 保留变量(GROK_HOOK_EVENT、GROK_SESSION_ID、CLAUDE_PROJECT_DIR……)——保留变量最后写、永远赢,用户没法伪造这些身份信号。 - 不泄密:
command/url都保留一份command_raw/url_raw原文用于展示,真正执行的是展开后的;reqwest 出错用without_url()剥掉可能带 token 的 URL。 - SSRF 防护(HTTP hook):只允许 HTTPS,解析目标 IP、拦私有网段与云元数据地址(
169.254.169.254),关掉重定向。防 hook 被诱导打内网。两个边界要知道:回环地址(127.0.0.0/8、::1)是有意放行的,方便本机调试;校验只在解析时做一次,挡不住 DNS rebinding。 - 子进程也设防:命令 hook 的子进程会套上沙箱的子进程网络限制(
restrict_child_network)。
插件带的 hooks 走一个适配器(hooks_adapter.rs):预过滤不支持的事件(以解析器认不认为准,不另维护白名单)、注入 ${CLAUDE_PLUGIN_ROOT} 等插件变量、给 hook 名字加 plugin/<名>/ 命名空间——不是第二套引擎,只是喂给同一个 hooks 解析器前的预处理。
和《从 LLM 到 Coding Agent》对照
对照 hooks 与 Claude Code 的 Hooks:最小实现里 hook 可能就是「工具前跑个回调」。grok 的增量在真实产品的周全:按事件区分的 gate(拦工具、拦 prompt、拦停止、改入参与输出)、fail-open 威胁模型、Claude 别名匹配、环境变量防伪、SSRF 防护、插件适配。下一章看把这些扩展打包分发的容器:插件。
上一篇:MCP 工具进注册表 · 下一篇:插件 · 清单与信任