文章

【笔记】Claude Code Hook 的工具调用控制与消息注入机制

【笔记】Claude Code Hook 的工具调用控制与消息注入机制

Claude Code Hook 的关键价值,不只是“在某个时机运行脚本”,而是让确定性程序进入概率性的 Agent 循环:宿主可以在生命周期节点读取实时状态、控制工具行为,并把反馈送进模型的后续推理。

这篇笔记重点回答三个问题:

  1. 一次事件如何经过 matcherif 找到真正要执行的 handler;
  2. Hook 怎样影响工具执行与权限链;
  3. additionalContext 怎样进入下一次模型请求。

本文不枚举所有 Hook 事件,也不把 Claude Code 当前的内部消息排版当作稳定协议。公开字段和行为以官方文档为准;attachment、<system-reminder> 和消息合并过程只用于理解当前实现。

1. 先把 Hook 放回完整的上下文装配模型

Claude Code 中,模型看到的内容不只来自用户输入。宿主会在不同生命周期,以不同方式装配额外上下文:

来源触发方式主要作用是否属于硬控制
CLAUDE.md、rules、memory会话启动或路径访问提供相对稳定的项目指令
文件变更 attachment已读文件被外部修改告诉模型旧快照已经变化
Skill用户或模型显式选择注入一段可参数化的工作流指令
Hook生命周期事件自动触发控制流程、改写数据或追加实时上下文取决于输出通道

前三类解决“模型应该知道什么”。Hook 还可以解决“宿主是否允许这件事发生”。这正是 Hook 与普通上下文加载机制的核心差别。

flowchart LR
    A[稳定指令<br/>CLAUDE.md] --> M[消息装配]
    B[按需指令<br/>Skill] --> M
    C[环境变化<br/>attachment] --> M
    D[实时反馈<br/>Hook additionalContext] --> M
    M --> L[下一次模型调用]

    H[Hook 控制与数据输出] --> R[本次宿主行为]
    R --> T[工具执行或拒绝]
    T --> M

因此,additionalContext 应理解为 Hook 的一条输出通道,而不是 Hook 的全部能力;Hook 也不应被归类成另一种 CLAUDE.md。

2. Hook 是 Agent 循环中的确定性控制点

Claude Code 可以粗略拆成模型和宿主两部分:

1
2
3
4
5
6
7
Claude 模型:推理并生成 tool_use
        ↓
Claude Code 宿主:检查权限、执行工具、维护消息
        ↓
工具结果:包装为 tool_result
        ↓
Claude 模型:读取反馈并继续推理

模型擅长开放式判断,但具有概率性,也不天然知道剩余时间、CI 状态、审批结果等实时信息。宿主掌握工具、权限和消息组装,可以提供确定性保证。

Hook 就是宿主暴露的生命周期扩展点:

1
2
3
4
5
6
7
8
9
10
11
12
13
生命周期事件
    ↓
matcher 等条件筛选
    ↓
执行 Hook handler
    ↓
解析退出码与结构化输出
    ├─ 控制宿主流程
    ├─ 修改工具输入或模型可见结果
    ├─ 向后续模型调用追加上下文
    └─ 记录日志、发送通知等外部动作
    ↓
继续或停止 Agent 循环

一句话记忆:

Hook 让外部确定性程序在 Agent 生命周期的指定位置观察、干预并反馈。

2.1 为什么 Prompt 不能替代 Hook

Prompt 可以要求模型不要运行危险命令,但是否遵守仍取决于模型。PreToolUse 的拒绝则由宿主执行,可以在副作用发生前阻止调用。

Prompt 也无法自动获得不断变化的外部状态。Hook 可以在事件发生时查询这些状态,再把结果变成:

  • 权限决定;
  • 修改后的工具参数;
  • 修改后的模型可见结果;
  • 下一轮推理所需的补充上下文。

因此,稳定原则可以留在 Prompt;必须强制执行的约束、实时状态和行动后的反馈更适合放在 Hook 或其他宿主机制中。

2.2 与 AOP 的类比边界

Claude CodeJava AOP作用
Hook 事件Join point预先定义的生命周期节点
matcherPointcut选择需要处理的事件或工具
Hook handlerAdvice真正执行的外部逻辑
Hook 输出Advice 结果控制流程、修改数据或追加上下文

这个类比只用于建立直觉。Hook 只能处理 Claude Code 明确定义的事件,不能拦截任意内部方法。

3. 先分清 run、turn、工具调用和 Observation

一次用户任务通常不是一次模型调用:

1
2
3
4
5
6
7
用户提交任务
│
├─ 模型调用 #1:生成 tool_use A、B
├─ 宿主执行 A、B,生成 tool_result A、B
├─ 模型调用 #2:读取结果,生成 tool_use C
├─ 宿主执行 C,生成 tool_result C
└─ 模型调用 #3:读取结果,输出最终答案
概念本文含义
Session一段可持续、可恢复的完整对话
Agent run一次用户任务从提交到最终返回
Agent turn模型调用工具、宿主执行并反馈结果的一次往返
Tool call一次具体的 Read、Bash、Edit 或 MCP 调用

经典 ReAct 中的 Observation,在这里主要对应工具执行产生的数据;tool_result 是它的协议载体。下一次 API 调用不是 Observation 本身,而是负责把 Observation 送回模型。

1
2
3
4
5
6
7
8
9
Reason → Action(tool_use)
              ↓
        宿主执行工具
              ↓
     Observation 在本地产生
              ↓
       包装成 tool_result
              ↓
下一次模型调用读取结果并继续 Reason

这个时间顺序决定了一个重要边界:PreToolUse 虽然发生在工具执行前,但模型已经生成了这次 tool_use。Pre Hook 返回的上下文不会让模型在工具执行前自动重推理一次。

4. Tool Use Hook 位于哪里

围绕一次工具调用,不能只看 Pre 和 Post。当前权限链还包括 PermissionRequest;auto mode 的拒绝另有 PermissionDenied,并行工具全部结束后还有批次级 PostToolBatch

flowchart TD
    A[模型生成 tool_use] --> B[PreToolUse]
    B -->|deny| C[拒绝结果反馈给模型]
    B -->|defer| X[保留调用并退出<br/>等待外部恢复]
    B -->|ask / allow / 默认流程| P[权限规则与模式继续判断]
    P -->|需要用户确认| D[PermissionRequest]
    D -->|deny| C
    D -->|allow| E[执行工具]
    P -->|允许| E
    P -->|auto mode 拒绝| Q[PermissionDenied]
    Q -->|可选 retry true| C
    E -->|成功| F[PostToolUse]
    E -->|失败| G[PostToolUseFailure]
    F --> J[等待同批工具全部结束]
    G --> J
    J --> K[PostToolBatch 恰好一次]
    K --> H[下一次模型调用]
    C --> H

PostToolUseFailure 只处理真正开始执行后发生的失败。输入校验失败、权限拒绝等情况有各自的路径,不能假设所有未成功调用都会触发它。

图中 PermissionDenied 不是所有拒绝事件的统一出口。当前官方契约下,它只在 auto mode 拒绝工具调用时触发;用户手动拒绝、PreToolUse 返回 deny 或配置中的 deny rule 命中,都不会触发它。

4.1 核心能力矩阵

能力PreToolUsePostToolUsePostToolUseFailure
读取工具参数可以可以可以
读取成功结果不可以可以不可以
读取执行错误不可以不可以可以
修改本次实际参数updatedInput不可以不可以
阻止本次执行permissionDecision: deny工具已经执行工具已经失败
替换模型看到的成功结果不可以updatedToolOutput不适用
追加后续模型上下文additionalContextadditionalContextadditionalContext
撤销现实副作用工具尚未执行不可以不可以

这张表的重点不是背字段,而是区分三个时间点:执行前可以改变现实行为,执行后只能改变反馈,追加上下文则要到下一次模型调用才被读取。

4.2 三个周边事件各自补哪一段

事件触发点适合做什么不能误解成什么
PermissionRequestClaude Code 即将弹出权限确认,或无法交互时原本会自动拒绝代表用户侧审批,允许、拒绝、修改输入或更新权限规则不是每次工具调用都会触发
PermissionDeniedauto mode 已经拒绝工具调用记录拒绝,或用 retry: true 告诉模型可以重试不会撤销本次拒绝,也不覆盖所有拒绝来源
PostToolBatch同一批全部工具调用已经结束、下一次模型请求之前基于整批结果汇总一次上下文,或阻止 Agent 继续循环不是每个工具都会触发一次

PermissionRequestPreToolUse 的位置不同。Pre Hook 在每次工具调用的权限判断前运行;PermissionRequest 只在确实需要权限决定时运行。它可以代替用户回答一次权限请求:

1
2
3
4
5
6
7
8
9
10
11
{
  "hookSpecificOutput": {
    "hookEventName": "PermissionRequest",
    "decision": {
      "behavior": "allow",
      "updatedInput": {
        "command": "npm run lint"
      }
    }
  }
}

即使返回 allow,修改后的参数仍会重新经过 deny 和 ask 规则;Hook 不能用一次允许决定绕过更严格的权限规则。

PermissionDeniedretry 也只是改变后续认知:

1
2
3
4
5
6
{
  "hookSpecificOutput": {
    "hookEventName": "PermissionDenied",
    "retry": true
  }
}

它会告诉模型可以调整后重试,但不会让刚刚被拒绝的调用恢复执行。若 auto mode 没有得到可用的分类结论,Claude Code 还可能忽略 retry: true,继续保持安全拒绝。

PostToolBatch 则解决并行工具的汇总问题。PostToolUsePostToolUseFailure 仍按单个工具触发;当整批调用全部 resolved 后,PostToolBatch 恰好触发一次,而且没有 matcher。它拿到整批 tool_calls,适合追加依赖整组结果才能形成的说明:

1
2
3
4
5
6
{
  "hookSpecificOutput": {
    "hookEventName": "PostToolBatch",
    "additionalContext": "这批读取都属于账本模块,完成前统一运行 pytest。"
  }
}

如果返回顶层 decision: "block"continue: false,它会在下一次模型调用前停止 Agent 循环。

5. Hook 输出有三条语义通道

只按“Pre 和 Post”分类还不够。Hook 输出实际影响三个不同对象:

通道影响对象典型字段
控制通道宿主是否继续或是否允许工具permissionDecisioncontinue
数据通道工具实际输入或模型可见结果updatedInputupdatedToolOutput
认知通道模型下一次推理所见信息additionalContext

5.1 控制通道:决定本次动作是否发生

PreToolUse 可以在副作用发生前拒绝调用:

1
2
3
4
5
6
7
{
  "hookSpecificOutput": {
    "hookEventName": "PreToolUse",
    "permissionDecision": "deny",
    "permissionDecisionReason": "禁止在 main 分支强制推送"
  }
}

它也可以返回 ask 强制进入用户确认,返回 allow 跳过普通交互式提示,或者在非交互调用中返回 defer,保留当前工具调用并等待外部宿主稍后恢复。

一次事件可能命中多条 Pre Hook。Claude Code 会先等待所有匹配的 Hook 运行完,再合并结果;其中一条返回 deny,不会阻止其它 sibling Hook 已经开始执行。因此,不能依赖“安全 Hook 先拒绝”来阻止另一条 Hook 自身产生副作用。

当前决策优先级是:

1
deny > defer > ask > allow

最严格的结果获胜,配置顺序不能让 allow 覆盖 deny。所有 Hook 返回的 additionalContext 会一起保留并交给模型。

多条 Hook 同时修改同一个 tool_input 则不安全:Hook 并行运行,顺序不确定,不应让最终结果依赖哪个 updatedInput 后合并。需要组合修改时,应收敛到同一个 handler 中按明确顺序处理。

Hook 自身的允许也不是权限系统的最高决定。deny 和 ask 规则仍会继续评估,所以 allow 可以减少普通提示,却不能绕过更严格的 deny rule。

顶层 continue: false 的含义更强:它停止 Claude Code 后续处理,而不只是拒绝某一次工具。stopReason 是给用户看的终止原因。

5.2 数据通道:改写实际输入或模型可见结果

Pre Hook 可以改写即将传给工具的参数:

1
2
3
4
5
6
7
8
{
  "hookSpecificOutput": {
    "hookEventName": "PreToolUse",
    "updatedInput": {
      "command": "npm test -- --runInBand"
    }
  }
}

Post Hook 可以用 updatedToolOutput 替换模型后续看到的成功结果。它适合脱敏、裁剪过大的输出,或把结果恢复成工具约定的结构。

但它不能撤销已经发生的文件写入、网络请求或其他副作用。替换模型可见结果,也不代表遥测和外部系统从未见过原始结果。

5.3 认知通道:给下一轮推理追加说明

additionalContext 保留原始工具结果,同时增加一段与当前事件有关的解释:

1
2
additionalContext:原始结果 + 补充说明
updatedToolOutput:原始结果 → 新的模型可见结果

一般应优先追加说明。只有确实需要脱敏、裁剪或修复输出结构时,才替换结果;不能借此静默伪造成功或隐藏影响判断的错误。

6. 为什么 Pre 的 additionalContext 不能改变本次决策

PreToolUse 开始时,本次模型调用已经结束,tool_use 已经生成。Hook 运行期间不会自动插入额外的模型调用:

1
2
3
4
5
6
7
模型调用 #1 已生成 tool_use
        ↓
PreToolUse 返回 additionalContext
        ↓
工具按权限决定执行或被拒绝
        ↓
模型调用 #2 才读取 additionalContext

因此:

Pre 输出影响对象生效时间
updatedInput本次工具的实际参数工具执行前
permissionDecision本次工具是否执行工具执行前
additionalContext模型后续认知下一次模型调用

如果目标是让模型放弃危险动作并重新选择方案,应该同时拒绝当前调用并给出替代方向:

1
2
3
4
5
6
7
8
{
  "hookSpecificOutput": {
    "hookEventName": "PreToolUse",
    "permissionDecision": "deny",
    "permissionDecisionReason": "当前命令可能破坏生产数据",
    "additionalContext": "请改用只读查询核验"
  }
}

只返回“这个命令可能很危险”的 additionalContext,并不会自动阻止本次工具。

7. additionalContext 怎样进入下一次模型请求

公开契约只保证补充内容会进入 Claude 的上下文。为了理解它为什么不是“修改 system prompt”,可以继续观察当前内部消息链:

1
2
3
4
5
6
7
Hook 结构化输出
  → 校验 hookEventName
  → 内部 hook_additional_context attachment
  → 包装成 <system-reminder> 文本
  → 转成 user role 下的模型可见内容
  → 与相邻 user 消息或 tool_result 规范化
  → 下一次 LLM API 调用读取

7.1 attachment 是内部中间表示

Claude Code 不必在收到 Hook 输出时就直接拼接最终 API Payload。它先保留一份带来源信息的内部对象,例如:

1
2
3
4
5
6
{
  type: "hook_additional_context",
  content: ["剩余时间不足,请开始收敛结论"],
  hookName: "PostToolUse:Read",
  hookEvent: "PostToolUse"
}

这样可以把事件处理与最终消息排版解耦。文件变化、Skill 引用和 Hook 提醒都可能经过 attachment 或类似的内部消息抽象,但它们的触发条件和语义来源仍然不同。

7.2 system-reminder 不是 API 顶层 system prompt

当前实现可能把补充内容包装成:

1
2
3
<system-reminder>
PostToolUse:Read hook additional context: 剩余时间不足,请开始收敛结论
</system-reminder>

这个标签表达“这是宿主注入的框架提醒”,不代表 Claude Code 修改了 Anthropic Messages API 的顶层 system 参数。

7.3 user role 不等于真人输入

工具结果本来就位于 API 的 user role。Hook 提醒也可能作为 user role 下的文本进入消息序列:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
  "role": "user",
  "content": [
    {
      "type": "tool_result",
      "tool_use_id": "toolu_123",
      "content": "文件内容"
    },
    {
      "type": "text",
      "text": "<system-reminder>...</system-reminder>"
    }
  ]
}

协议角色只描述消息在对话序列中的位置,不能据此判断内容来自真人,也不能据此推导它具有更高权限。

当前内部实现还可能把紧邻工具结果的提醒折叠进 tool_result.content,避免形成额外的对话边界。因此最稳妥的结论是:

additionalContext 在语义上成为模型可见的补充文本;它在最终请求中是独立 text block,还是并入相邻 tool_result,属于版本相关的内部排版。

业务代码不应解析 <system-reminder> 的具体文本格式。

8. 输入、退出码与结构化输出

8.1 输入是由事件名判别的平铺 JSON

command Hook 从 stdin 读取一层平铺对象。公共字段与事件专属字段并排出现,通过 hook_event_name 区分事件:

1
2
3
4
5
6
7
8
9
10
{
  "session_id": "8f2c1e6a",
  "transcript_path": "/path/to/transcript.jsonl",
  "cwd": "/workspace",
  "permission_mode": "default",
  "hook_event_name": "PreToolUse",
  "tool_name": "Bash",
  "tool_input": { "command": "npm test" },
  "tool_use_id": "toolu_123"
}

不同事件会替换专属字段。例如 UserPromptSubmit 提供 promptPostToolUseFailure 提供 error

8.2 matcher 与 if 是粗筛和细筛

Hook 配置先按事件分组,再经过两层不同粒度的筛选:

  • matcher 位于 {matcher, hooks} 这一层,决定整组 handler 是否参与当前事件。Tool Use 事件通常按 tool_name 粗筛,例如只处理 Bash;其他事件可能匹配各自的判别字段。省略 matcher 或使用 * 表示当前事件全部匹配,不表示跨越所有 Hook 事件。
  • if 位于单个 handler 上,使用权限规则语法同时匹配工具名与参数,例如 Bash(rm *)。它在进程启动前细筛,可以避免每次 Bash 调用都启动脚本。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
{
  "hooks": {
    "PreToolUse": [{
      "matcher": "Bash",
      "hooks": [
        {
          "type": "command",
          "if": "Bash(git push --force*)",
          "command": "~/scripts/check-force-push.sh"
        },
        {
          "type": "command",
          "if": "Bash(npm install*)",
          "command": "~/scripts/check-npm-install.sh"
        }
      ]
    }]
  }
}

一次 git push --force origin main 先以 tool_name: "Bash" 命中 matcher,再以具体命令命中第一条 if;第二条 handler 不会启动。两层关系是:

1
2
3
4
事件发生
  → matcher 判断这一组是否参与
    → if 判断组内哪一条 handler 真正运行
      → handler 读取完整输入并作最终决定

if 只适用于工具事件:PreToolUsePostToolUsePostToolUseFailurePermissionRequestPermissionDenied。放在其他事件上会导致该 handler 不运行。

它还有两个边界:

  1. 一条 if 只能写一条权限规则,没有 &&|| 或列表语法;需要多条条件时应拆成多个 handler,或把复杂判断放进 handler;
  2. Bash 中的变量、命令替换和复合命令会让静态判断变复杂。Claude Code 无法确定实际子命令时可能保守地运行 Hook,因此 if 是减少无关 handler 启动的筛选器,不是硬安全边界。真正的允许与拒绝仍应由权限系统或 handler 的确定性校验完成。

8.3 退出码先决定输出怎样解释

command Hook 通过 stdout、stderr 和退出码通信:

退出码基本语义
0成功;若需要决策或注入模型上下文,应返回符合事件 schema 的 JSON
2阻止型错误;在该事件支持阻止时停止动作,并把 stderr 作为反馈
其他非零值非阻止错误;通常记录错误并继续流程

最容易踩的坑是把 exit 1 当作拒绝。真正需要拒绝时,应使用 exit 2 或事件对应的结构化决策字段。

工具类 Hook 成功执行后的普通 stdout 不应被当成可靠的模型消息通道;当前官方文档将其视为调试日志。需要让模型读取内容时,明确返回 hookSpecificOutput.additionalContext

8.4 通用 JSON 字段与事件字段不要混用

常见顶层字段包括:

字段作用
continuefalse 时终止 Claude 后续处理
stopReason终止时给用户看的原因
suppressOutput是否隐藏 Hook 的普通输出
systemMessage给用户显示的提示,不是模型上下文
hookSpecificOutput承载当前事件专属字段

hookSpecificOutput.hookEventName 必须与实际事件一致。additionalContext 也应放在该对象内部;放在 JSON 根部会被忽略。

9. 一个完整应用:时间预算控制

时间预算是三条通道协作的典型场景:

1
2
3
4
5
6
7
8
PostToolUse / PostToolUseFailure additionalContext
  → 告诉模型剩余时间,推动主动收敛

PreToolUse permissionDecision
  → 进入最终阶段后拒绝新工具,阻止继续扩张

任务级 deadline
  → 时间耗尽时强制终止请求

三层分别解决“提醒收敛”“禁止继续行动”和“最终截止”。它们不能互相替代:

  • 只有 additionalContext,模型仍可能忽略提醒;
  • 只有 Pre Hook,无法打断已经运行的工具或正在进行的模型生成;
  • 只有 deadline,Agent 可能在来不及整理结论时被直接终止。

成功和失败事件都应考虑。连续失败同样消耗预算;如果只监听 PostToolUse,Agent 可能在重试中持续耗时,却收不到新的时间反馈。

10. 设计边界

10.1 additionalContext 不是安全边界

它只是模型可见信息:

  • 模型可能误解或忽略;
  • 文本会占用上下文窗口;
  • 高频重复提醒可能稀释真正重要的信息;
  • 会话压缩和消息规范化可能改变最终排版。

危险操作应使用 Pre 的确定性决定,敏感信息应在数据边界脱敏,硬超时应由 deadline 执行。

10.2 Post Hook 不能撤销副作用

Post Hook 可以改变模型看到的结果,也可以追加解释,但工具已经执行。需要防止某件事发生,控制点必须前移到 Pre Hook、权限系统或沙箱。

10.3 不把内部消息结构当作公开协议

attachment 类型、<system-reminder> 文案、连续 user 消息合并和文本折叠都可能变化。外部集成应依赖官方 Hook 输入输出 schema,而不是解析 transcript 中的内部包装。

10.4 CLI 与 SDK 版本需要分别核验

当前 CLI 文档支持的事件或字段,不代表旧版 Agent SDK 的语言绑定已经暴露同名类型。集成时至少核对:

  • Claude Code CLI 版本;
  • Agent SDK 及语言绑定版本;
  • 当前事件支持的输入输出字段;
  • 工具输出的具体 schema;
  • Hook 触发路径是否覆盖成功、失败和权限拒绝。

11. 测试重点

  • Hook 是否注册到正确事件和 matcher;
  • hookEventName 是否与事件一致;
  • updatedInput 是否真正传给本次工具;
  • deny 是否在副作用发生前阻止调用;
  • matcher 与 if 是否分别命中了预期的工具类别和具体参数;
  • 多个 Pre Hook 冲突时,是否按 deny > defer > ask > allow 合并,且测试没有依赖执行顺序;
  • PermissionRequest 是否只在真实权限请求路径触发;
  • PermissionDenied 的测试是否运行在 auto mode,并确认 retry 不会恢复已拒绝调用;
  • 并行工具是否逐个触发 Post Hook、整批结束后只触发一次 PostToolBatch
  • Pre 的 additionalContext 是否被误写成“执行前触发模型重推理”;
  • Post 替换结果是否满足工具输出 schema;
  • 模型可见结果被替换后,是否有人误以为现实副作用也被撤销;
  • 成功、执行失败、权限拒绝和用户中断是否进入预期路径;
  • Hook 错误是否泄露凭据或未截断的大结果;
  • 升级 CLI 或 SDK 后,关键字段和触发时序是否重新核验。

12. 复习入口

12.1 最短心智模型

1
2
3
4
5
6
7
8
9
模型生成 tool_use
        ↓
Pre 控制本次工具如何执行
        ↓
工具产生 Observation
        ↓
Post 控制结果如何反馈给模型
        ↓
下一次模型调用消费 tool_result 与 additionalContext
1
2
3
Pre 主要改变现实行为
Post 主要改变执行后的模型认知
additionalContext 只在下一次模型调用中被读取

12.2 易混点

  • Observation 是工具反馈,下一次 API 调用负责运输和消费它;
  • tool_result 位于 user role,不等于真人输入;
  • Pre 的上下文在执行前产生,但下一次模型调用才读取;
  • 只返回 Pre additionalContext 不会自动阻止工具;
  • Post 可以替换模型可见结果,不能撤销现实副作用;
  • matcher 先按事件判别字段粗筛,if 再按工具名和参数细筛;
  • PermissionRequest 处理即将发生的权限确认,PermissionDenied 只处理 auto mode 已经发生的拒绝;
  • PostToolBatch 在整批工具结束后只触发一次,适合汇总而不是单工具反馈;
  • 多个 Pre Hook 都会执行,权限决定按 deny > defer > ask > allow 合并;
  • additionalContext 是认知通道,不是权限或安全边界;
  • <system-reminder> 是当前内部包装,不等于 API 顶层 system prompt;
  • 普通 stdout 不是 Tool Use Hook 向模型注入内容的可靠通道;
  • exit 1 不等于拒绝,真正阻止应使用 exit 2 或结构化决策;
  • 当前 CLI 文档不能倒推旧 SDK 已支持同一字段。

13. 核验入口

本文由作者按照 CC BY 4.0 进行授权