Appearance
Gemini CLI Hooks 通过 stdin 接收 JSON 输入,通过 stdout 返回 JSON 决策。exit 0 解析 stdout 为结构化决策;exit 2 触发紧急阻断(stderr 作为拒绝原因);其他 exit code 产生非致命警告。本文是所有 Hook 事件 I/O Schema 的权威参考。
Hooks 参考文档
本文档是 Gemini CLI Hooks 系统的完整技术规范,适合需要精确字段定义的高级用户。
关于如何编写第一个 Hook 的入门教程,见 Hooks 编写指南;关于 Hook 的概念介绍,见 Hooks 系统。
全局机制
- 通信方式:
stdin接收 JSON 输入,stdout输出 JSON 结果,stderr输出日志 - 黄金规则:
stdout只能输出最终 JSON,任何额外文本(哪怕一个echo)都会导致解析失败
退出码
| 退出码 | 行为 |
|---|---|
0 | 成功。解析 stdout 为 JSON,所有逻辑(包括 deny)都应使用此码 |
2 | 系统阻断。目标操作被中止,stderr 内容作为拒绝原因 |
| 其他 | 非致命警告。展示警告但继续执行 |
settings.json 配置结构
每个事件名下是一个 Hook 定义数组:
json
{
"hooks": {
"BeforeTool": [
{
"matcher": "write_file",
"sequential": false,
"hooks": [
{
"type": "command",
"command": "bash .gemini/hooks/check.sh",
"name": "security-check",
"timeout": 30000,
"description": "安全扫描"
}
]
}
]
}
}Hook 定义字段
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
matcher | string | 否 | 正则表达式(工具类)或精确字符串(生命周期类),匹配时触发 |
sequential | boolean | 否 | true 时同组 Hook 串行执行,false(默认)并行 |
hooks | array | 是 | Hook 配置数组 |
Hook 配置字段
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
type | string | 是 | 执行引擎,目前只支持 "command" |
command | string | 是(type=command 时) | 要执行的 Shell 命令 |
name | string | 否 | Hook 名称,用于日志和 /hooks 命令 |
timeout | number | 否 | 超时毫秒数,默认 60000 |
description | string | 否 | Hook 用途说明 |
基础输入 Schema(所有 Hook 共用)
每个 Hook 通过 stdin 接收到的公共字段:
typescript
{
"session_id": string, // 当前会话的唯一 ID
"transcript_path": string, // 会话记录的绝对路径(JSON 文件)
"cwd": string, // 当前工作目录
"hook_event_name": string, // 触发的事件名,如 "BeforeTool"
"timestamp": string // ISO 8601 执行时间
}通用输出字段
大多数 Hook 支持以下 stdout JSON 字段:
| 字段 | 类型 | 说明 |
|---|---|---|
systemMessage | string | 立即显示给用户的消息(终端内) |
suppressOutput | boolean | true 时从日志/遥测中隐藏 Hook 元数据 |
continue | boolean | false 时立即停止整个 Agent 循环 |
stopReason | string | continue: false 时显示给用户的原因 |
decision | string | "allow" 或 "deny"(别名 "block"),具体影响见各事件说明 |
reason | string | decision: "deny" 时的反馈/错误信息 |
工具类 Hook
BeforeTool
工具被调用前触发,用于参数验证、安全检查、参数改写。
Matcher 匹配规则:
- 内置工具:直接用工具名,如
read_file、run_shell_command - MCP 工具:
mcp_<server_name>_<tool_name> - 支持正则:
read_.*匹配所有读取类工具
专属输入字段:
| 字段 | 类型 | 说明 |
|---|---|---|
tool_name | string | 被调用的工具名 |
tool_input | object | 模型生成的原始参数 |
mcp_context | object | MCP 工具的上下文元数据(可选) |
original_request_name | string | 尾部工具调用时的原始工具名 |
专属输出字段:
| 字段 | 说明 |
|---|---|
decision: "deny" | 阻止工具执行,reason 文本发送给 Agent 作为工具错误,Agent 可据此重试 |
hookSpecificOutput.tool_input | 与模型参数合并,用于改写工具参数 |
continue: false | 立即终止整个 Agent 循环 |
exit 2 行为:阻止工具执行,stderr 作为发给 Agent 的 reason。Turn 继续。
AfterTool
工具执行完成后触发,用于结果审计、上下文附加或对 Agent 隐藏敏感输出。
专属输入字段:
| 字段 | 类型 | 说明 |
|---|---|---|
tool_name | string | 工具名 |
tool_input | object | 原始调用参数 |
tool_response | object | 工具执行结果,含 llmContent、returnDisplay、error |
mcp_context | object | MCP 上下文 |
original_request_name | string | 尾部调用时的原始工具名 |
专属输出字段:
| 字段 | 说明 |
|---|---|
decision: "deny" | 向 Agent 隐藏真实工具输出,reason 替换工具结果 |
hookSpecificOutput.additionalContext | 追加到工具结果后,附加上下文发给 Agent |
hookSpecificOutput.tailToolCallRequest | { name: string, args: object }:立即执行一个"尾调用"工具,其结果替换原工具响应 |
continue: false | 立即终止整个 Agent 循环 |
exit 2 行为:隐藏工具结果,stderr 作为替代内容发给 Agent。Turn 继续。
Agent 类 Hook
BeforeAgent
用户提交 Prompt 后、Agent 开始规划前触发。用于上下文注入或 Prompt 验证。
专属输入字段:
| 字段 | 说明 |
|---|---|
prompt | 用户提交的原始文本 |
专属输出字段:
| 字段 | 说明 |
|---|---|
hookSpecificOutput.additionalContext | 追加到本轮 Prompt 的额外上下文 |
decision: "deny" | 阻断本轮并丢弃用户消息(不进历史) |
continue: false | 阻断本轮但保留消息到历史 |
reason | 拒绝或停止时必填 |
exit 2 行为:阻断 Turn,从上下文清除 Prompt。等同于 decision: "deny"。
AfterAgent
每轮 Agent 生成最终响应后触发,是响应验证与自动重试的主要接入点。
专属输入字段:
| 字段 | 说明 |
|---|---|
prompt | 用户的原始请求 |
prompt_response | Agent 生成的最终文本响应 |
stop_hook_active | true 表示此 Hook 正在重试序列中运行 |
专属输出字段:
| 字段 | 说明 |
|---|---|
decision: "deny" | 拒绝响应,reason 作为新 Prompt 发送给 Agent,触发自动重试 |
continue: false | 停止会话,不重试 |
hookSpecificOutput.clearContext | true 时清空对话历史(LLM 内存),UI 显示保留 |
exit 2 行为:拒绝响应,stderr 作为反馈 Prompt 触发自动重试。
模型类 Hook
BeforeModel
向 LLM 发起请求前触发,操作稳定的 SDK 无关请求格式。
专属输入字段:
| 字段 | 说明 |
|---|---|
llm_request | 包含 model、messages、config(生成参数)的请求对象 |
专属输出字段:
| 字段 | 说明 |
|---|---|
hookSpecificOutput.llm_request | 覆盖部分请求字段(如切换模型、调整 temperature) |
hookSpecificOutput.llm_response | 合成响应对象:提供后跳过真实 LLM 调用,直接用此响应 |
decision: "deny" | 阻止请求,中止本轮 |
exit 2 行为:中止 Turn,跳过 LLM 调用,stderr 作为错误消息。
BeforeToolSelection
LLM 决定调用哪些工具前触发,用于过滤可用工具集或强制特定工具模式。
专属输入字段:
| 字段 | 说明 |
|---|---|
llm_request | 同 BeforeModel 格式 |
专属输出字段:
| 字段 | 说明 |
|---|---|
hookSpecificOutput.toolConfig.mode | "AUTO" / "ANY" / "NONE"。"NONE" 禁用所有工具(优先级最高);"ANY" 强制调用至少一个工具 |
hookSpecificOutput.toolConfig.allowedFunctionNames | 工具名白名单 |
多 Hook 合并策略:多个 BeforeToolSelection Hook 的白名单取并集,只有 mode: "NONE" 能覆盖其他 Hook 禁用全部工具。
限制:不支持 decision、continue、systemMessage。
AfterModel
LLM 响应 chunk 接收后立即触发,用于实时内容脱敏或 PII 过滤。
专属输入字段:
| 字段 | 说明 |
|---|---|
llm_request | 原始请求 |
llm_response | 模型响应(流式时为当前 chunk) |
专属输出字段:
| 字段 | 说明 |
|---|---|
hookSpecificOutput.llm_response | 替换当前响应 chunk |
decision: "deny" | 丢弃当前 chunk,阻断本轮 |
continue: false | 立即终止整个 Agent 循环 |
流式注意:AfterModel 在每个流式 chunk 触发一次,修改只影响当前 chunk。
exit 2 行为:中止 Turn,丢弃模型输出,stderr 作为错误消息。
生命周期 Hook
SessionStart
应用启动、恢复会话或 /clear 命令后触发,用于加载初始上下文。
专属输入字段:
| 字段 | 说明 |
|---|---|
source | "startup" / "resume" / "clear" |
专属输出字段:
| 字段 | 说明 |
|---|---|
hookSpecificOutput.additionalContext | 交互模式:注入为首轮历史;非交互模式:前置到 Prompt |
systemMessage | 会话开始时显示给用户 |
注意:advisory only,continue 和 decision 被忽略,启动不可被阻断。
SessionEnd
CLI 退出或会话清除时触发,用于清理或最终遥测。
专属输入字段:
| 字段 | 说明 |
|---|---|
reason | "exit" / "clear" / "logout" / "prompt_input_exit" / "other" |
专属输出字段:
| 字段 | 说明 |
|---|---|
systemMessage | 关闭时显示给用户 |
注意:CLI 不等待此 Hook 完成,忽略所有流程控制字段。
Notification
CLI 触发系统提示(如工具权限弹窗)时触发,用于外部日志或跨平台通知。
专属输入字段:
| 字段 | 说明 |
|---|---|
notification_type | 当前只有 "ToolPermission" |
message | 提示摘要 |
details | 提示相关元数据(工具名、文件路径等)JSON 对象 |
专属输出字段:
| 字段 | 说明 |
|---|---|
systemMessage | 与系统提示一起显示 |
注意:此 Hook 不能阻止弹窗或自动授权,仅观测用。
PreCompress
CLI 在压缩历史记录以节省 Token 前触发,用于日志记录或状态保存。
专属输入字段:
| 字段 | 说明 |
|---|---|
trigger | "auto" 或 "manual" |
专属输出字段:
| 字段 | 说明 |
|---|---|
systemMessage | 压缩前显示给用户 |
注意:异步触发,不能阻断或修改压缩过程。
稳定模型 API(Stable Model API)
Gemini CLI 使用以下 SDK 无关格式,确保 SDK 升级后 Hook 脚本不需修改:
LLMRequest
typescript
{
"model": string,
"messages": Array<{
"role": "user" | "model" | "system",
"content": string // 非文本内容(图片等)在此处被过滤掉
}>,
"config": { "temperature": number, ...生成参数 },
"toolConfig": { "mode": string, "allowedFunctionNames": string[] }
}LLMResponse
typescript
{
"candidates": Array<{
"content": { "role": "model", "parts": string[] },
"finishReason": string
}>,
"usageMetadata": { "totalTokenCount": number }
}常见问题
Q: BeforeTool 的 decision: "deny" 和 continue: false 有什么区别?
A: decision: "deny" 只阻止当前工具调用,reason 以工具错误形式发给 Agent,Agent 可以看到错误并决定是否重试;continue: false 立即终止整个 Agent 循环,当前 Turn 直接结束,不给 Agent 任何响应机会。
Q: Hook 脚本超时了会怎样?
A: 超出 timeout(默认 60000ms)后,CLI 将该 Hook 视为非致命失败(相当于 exit 1),输出警告并用原始参数继续执行。如需更长时间,在 hooks 配置中显式设置 "timeout": 120000。
Q: 如何在不改代码的情况下临时禁用一个 Hook?
A: 在 settings.json 中将对应 Hook 的 command 改为 echo '{}',或删除该 Hook 条目并保存文件——Gemini CLI 会在下次触发时热重载配置,无需重启。