# Hooks · 在生命周期事件上挂确定性逻辑

> Hooks 让你在「调工具前/后、会话开始/结束」这些生命周期点上挂脚本或 HTTP 回调——用确定性代码补模型「只靠自觉」的不足。这一章看事件、两种 handler、各事件能拦什么（工具调用、用户 prompt、停止）、fail-open 取向，以及那些安全设计。

- 作者：David（道雾轩）
- 专栏：Grok Build 源码解读（https://daiw.org/manual/grok-build.md）
- 最后更新：2026-09-15
- 原文：https://daiw.org/manual/grok-build/hooks
- 转载与引用：请注明出处并附原文链接（https://daiw.org/about/copyright）

# 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](https://daiw.org/manual/grok-build/mcp-client) 是 fail-closed——**按威胁模型逐处选择**，是这套代码反复出现的成熟判断。)

## 匹配：又是 Claude 兼容

hook 用 `matcher` 决定对哪些工具生效。`HookMatcher` 分**精确**(简单的 `Bash|Edit` 形式，还会展开出 grok 别名)和**正则**两种。妙在它接 [claude_alias.rs](https://daiw.org/manual/grok-build/agent-interop)：你写 `^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](https://daiw.org/manual/llm-to-agent/hooks) 与 [Claude Code 的 Hooks](https://daiw.org/manual/claude-code/commands-customization)：最小实现里 hook 可能就是「工具前跑个回调」。grok 的增量在真实产品的周全：按事件区分的 gate(拦工具、拦 prompt、拦停止、改入参与输出)、fail-open 威胁模型、Claude 别名匹配、环境变量防伪、SSRF 防护、插件适配。下一章看把这些扩展打包分发的容器：插件。

---

上一篇：[MCP 工具进注册表](https://daiw.org/manual/grok-build/mcp-registry) · 下一篇：[插件 · 清单与信任](https://daiw.org/manual/grok-build/plugins-manifest)
