Yoda
参考Agent 设计指南扩展

Hooks

CC 与 Codex 的 hooks 已高度同构(事件命名都对齐),分歧在管控粒度——Codex 有 per-hook 开关与内容 hash 信任链,CC 只有插件级/全局开关但事件面大 2.7 倍

Hooks

结论

Hooks 是两家收敛最彻底的扩展点:Codex hooks 于 2026-05-14 GA,10 个事件与 Claude Code 的事件命名直接对齐(PreToolUse/PostToolUse/PreCompact/SubagentStart/Stop…),exit code 语义也一致。真正的分歧有三处:管控粒度(Codex 支持 per-hook 开关 + 基于内容 hash 的信任门,CC 明确不支持禁用单个 hook);事件面(CC 27 个事件 vs Codex 10 个);可观测性(双方都有内置监听子系统,Codex 默认开且带 OTel 指标,CC 的 hookEvents.ts 子系统在文档里完全没写)。harness 做跨 runtime hooks 统一抽象是踩在收敛方向上的,但注入 hooks 时必须处理 Codex 的信任关。

本章证据基线:Codex 源码 commit 2026-06-06(codex/codex-rs/)、CC 重建源码 src_2026-03-31、官方文档 2026-06 抓取。核心结论复用已验证的 golden example(见参考资料)。

研究问题

  • 两家的事件集、handler 类型、配置层叠语义各是什么?
  • 能否禁用单个 hook?批量管控的粒度差多少?
  • Codex 内容 hash 信任机制如何工作?harness 注入 hooks 如何过信任关?
  • runtime 是否内置 hook 执行的监听/遥测?

各 Agent 设计与实现

Claude Code

事件面:27 个事件。 [一手源码] src_2026-03-31/entrypoints/sdk/coreTypes.ts HOOK_EVENTS:PreToolUse、PostToolUse、PostToolUseFailure、Notification、UserPromptSubmit、SessionStart、SessionEnd、Stop、StopFailure、SubagentStart、SubagentStop、PreCompact、PostCompact、PermissionRequest、PermissionDenied、Setup、TeammateIdle、TaskCreated、TaskCompleted、Elicitation、ElicitationResult、ConfigChange、WorktreeCreate、WorktreeRemove、InstructionsLoaded、CwdChanged、FileChanged。

Handler 类型:6 种。 [一手源码] src_2026-03-31/schemas/hooks.ts:配置可写 command / prompt / agent / http 四种,另有 SDK 专用 callback 与会话内 function。每个 hook 的字段集为 type / command|prompt|url / if / shell / timeout / statusMessage / once / async / asyncRewake——没有 name/description/id 字段,唯一人类可读字段是 statusMessage(spinner 文案)。

层叠:5 层 settings + plugin + skill/agent frontmatter,叠加(concat + dedup)而非覆盖。 [一手文档] claude-code-docs/docs/hooks.mdsettings.md:"Array settings merge across scopes ... concatenated and deduplicated, not replaced."。同事件多个 hook 全部执行,权限裁决最严者胜(deny > ask > allow)。[一手源码] dedup 键是 ${pluginRoot ?? skillRoot ?? ''}\0${payload}utils/hooks.ts:1453):同一命令在 user+project 会去重,在两个不同插件中不会去重。

批量开关:只有插件级和全局级。 [一手文档] hooks.md 原文:"There is no way to disable an individual hook while keeping it in the configuration."。全局 "disableAllHooks": true(managed 层 hooks 不受其约束);插件级用 enabledPlugins 关掉整个插件的 hooks。matcher 只是组织手段,不是开关。另有未见于文档的 strictPluginOnlyCustomization 策略:封锁 user/project/local hooks、只留插件 hooks(utils/hooks/hooksConfigSnapshot.ts)。

可观测性:有完整的内置监听子系统,文档只字未提。 [一手源码] src_2026-03-31/utils/hooks/hookEvents.tsemitHookStarted / emitHookProgress(约 1s 轮询 stdout/stderr)/ emitHookResponse({exitCode, outcome}),外加单槽位 registerHookEventHandler(注册前事件排队)。beta tracing 打开时还会发 OTel 事件 hook_execution_startutils/hooks.ts:2076)。文档只提 claude --debug——文档少描述了整整一个子系统。

源码才有的行为细节 [一手源码]:

  • asyncRewakeschemas/hooks.ts):hook 后台运行,exit code 2 时唤醒模型并入队任务通知(utils/hooks.ts:236);
  • per-hook shell: 'bash'|'powershell',Windows 下 .sh 自动加 bash 前缀(utils/hooks.ts:790,862,873);
  • HTTP hooks 在 SessionStart/Setup 上被过滤——headless 模式下 sandbox ask-callback 会死锁(utils/hooks.ts:1850);
  • matcher 对新旧工具名都做正则匹配,如 TaskTasksutils/hooks.ts:1346)。

Codex CLI

事件面:10 个事件。 [一手源码] codex/codex-rs/protocol/src/protocol.rs:1331

pub enum HookEventName {
    PreToolUse, PermissionRequest, PostToolUse,
    PreCompact, PostCompact, SessionStart,
    UserPromptSubmit, SubagentStart, SubagentStop, Stop,
}

与 CC 同名同义,CC 的 27 事件是其超集(Codex 缺 SessionEnd/Notification/FileChanged/Setup 等)。Handler 类型 3 种:Command 已实现,Prompt/Agent 是空结构体占位(config/src/hook_config.rs:137)。

层叠:5+ 来源,纯叠加。 [一手源码] protocol.rs:1368 HookSource:System / User / Project / Mdm / SessionFlags / Plugin / CloudRequirements / CloudManagedConfig / Legacy…。hooks/src/engine/discovery.rsLowestPrecedenceFirst 遍历配置层,把每层的 handler append 进 vec(递增 display_order),无去重/替换步骤。优先级只决定执行顺序和信任强制(managed 层 is_managed=true 免信任审查),不产生覆盖。

管控粒度:四级,含 per-hook。 [一手源码]

  1. per-hook:config/src/hook_config.rs:20 HooksToml.state: BTreeMap<String, HookStateToml>,每项 { enabled: Option<bool>, trusted_hash: Option<String> },键为 {config_file_path}:{event}:{group_index}:{handler_index}hooks/src/lib.rs:100):
[state."/tmp/hooks.json:pre_tool_use:0:0"]
enabled = false
  1. 信任门:protocol.rs:1385 HookTrustStatus { Managed, Untrusted, Trusted, Modified }。只有 Managed/Trusted 会执行;脚本内容一改,hash 不匹配 → Modified → 阻断执行,直到用户在 /hooks 里重新 review。这是 CC 完全没有的机制。
  2. managed-only:discovery.rs HookDiscoveryPolicy.allow_managed_hooks_only,一键禁用所有非 managed hooks([一手文档] codex/docs/config.md:该开关只在 requirements.toml 生效,写进 config.toml 无效)。
  3. 插件分组:hooks 按 plugin_id 归组。

可观测性:零埋点、默认全开。 [一手源码] core/src/hook_runtime.rs:每次执行自动发 HookStarted/HookCompleted(带 duration_ms、stdout/stderr 按 Warning/Error/Feedback/Context/Stop 分类的 entries),记 OTel counter codex.hooks.run + histogram codex.hooks.run.duration_msotel/src/metrics/names.rs:47),并打 analytics 事实 HookRunFacthook_runtime.rs:661)。

差异矩阵

维度Claude CodeCodex CLI
事件数27(coreTypes.ts10(protocol.rs:1331
Handler 类型6:command/prompt/agent/http/callback/function3:Command 已实现,Prompt/Agent 占位
层叠语义叠加(concat+dedup,按 source root 去重)叠加(append,无去重)
禁用单个 hook不支持(文档明确)支持:state."key".enabled = false
信任机制无(靠 settings 文件本身的信任)内容 hash:改动即 Modified 阻断,需 /hooks 重审
批量开关disableAllHooks + enabledPluginsper-hook / trust / managed-only / plugin 四级
内置监听有(hookEvents.ts;OTel 需 beta tracing)有(事件 + OTel 指标 + analytics,默认开)
hook 命名/描述字段无,仅 statusMessage无,仅 status_message(机器 key 不可读)
后台 hook 唤醒模型有:asyncRewake(exit 2 唤醒)未见等价物

最小复现

# CC:验证 hooks 叠加而非覆盖——user 与 project 各放一个 PreToolUse hook
# ~/.claude/settings.json 与 .claude/settings.json 各配:
# {"hooks":{"PreToolUse":[{"matcher":"Bash","hooks":[{"type":"command","command":"echo layer-X >> /tmp/hook.log"}]}]}}
claude --debug -p "run: ls"
# 预期 /tmp/hook.log 出现两行(叠加语义;本结论来自 merge 源码 + 文档,未在本机重测)

# Codex:验证信任门——/hooks 信任某脚本后修改其内容,再触发
# 预期:hook 状态变 Modified,被跳过,直到重新 review

Harness 接入建议(Yoda 实践)

Yoda 的 harness 视图已用声明式注册表描述每个 runtime 的配置面(yoda/src/renderer/features/projects/components/harness-view/harness-spec.ts),hooks 检测应在此基础上扩展:

  1. 检测:CC 扫 5 层 settings 的 hooks 键 + 插件 hooks/hooks.json + skill/agent frontmatter;Codex 扫 config.toml 各层。展示时必须按"叠加后的有效集合"渲染,而不是按文件渲染——两家都是 stack 语义,按文件展示会让用户误以为高层覆盖低层。
  2. 校验:CC 按 schemas/hooks.ts 字段集做 schema 校验(没有 name 字段,UI 标签只能用 statusMessage 或命令摘要);Codex 校验 TOML 结构 + 计算脚本内容 hash 与 state.*.trusted_hash 比对,提前标出哪些 hook 会因 Modified 被静默跳过——这是用户最难自查的故障。
  3. 注入:harness 替用户写 hooks 时,Codex 侧必须同步写 trusted_hash 或引导用户走 /hooks 重审,否则注入即失效;CC 侧注意 dedup 按 source root 计算,同命令写到 user+project 只生效一份。
  4. 调试:CC SDK 模式可注册 registerHookEventHandler 拿 started/progress/response 流;Codex 直接消费 HookStarted/HookCompleted 事件,无需自行埋点。

失效条件

  • Codex Prompt/Agent handler 从占位变为实现(hook_config.rs 中结构体非空时,handler 对比过期)
  • CC 开放 per-hook 禁用(docs/hooks.md 中 "no way to disable an individual hook" 表述消失)
  • CC HOOK_EVENTS 增删(重建源码滞后线上约 2 个月,27 这个数需对新版本回归)
  • Codex 信任机制改为签名/白名单等非内容 hash 方案

参考资料

  • Golden example(已验证):agent-research/.claude/skills/agent-research/references/golden-example-hooks.md
  • Codex 源码:codex/codex-rs/protocol/src/protocol.rs(HookEventName/HookSource/HookTrustStatus)、codex-rs/config/src/hook_config.rscodex-rs/hooks/src/engine/discovery.rscodex-rs/core/src/hook_runtime.rs
  • CC 源码:src_2026-03-31/schemas/hooks.tsutils/hooks.tsutils/hooks/hookEvents.tsentrypoints/sdk/coreTypes.ts
  • 文档:claude-code-docs/docs/hooks.mdhooks-guide.mdhttps://developers.openai.com/codex/hooks(GA 2026-05-14)

On this page