背景
#238(install_hook_rule / uninstall_hook_rule,实现见 #265)为 codex 落地了 HTTP-source agent hook。claude 格式用条目上的 description 写入专属 marker [teamai:agent-hook:<slug>],卸载时按 slug 精确匹配。但 codex 的单个 hook 条目没有任何可承载 slug 的标识字段,导致两个问题。
问题
1. 「每个 codex slug 必须用唯一 cmd」是一个不友好的对外约束。
codex 条目只能靠 command 字符串区分。若两个不同 slug 用了相同 cmd,卸载其一时会按 command === "..." 误删另一个。目前只能在文档里要求后端「每个 codex slug 用唯一 cmd」来规避。
2. teardown 对 codex 不对称、依赖 manifest 完整性。
- claude agent hook 靠
[teamai:agent-hook: marker,即使本地 manifest(~/.teamai/local-agent/agent-hooks.json)丢失,reconcileHooks(removeAll) 仍能扫描 marker 清除残留。
- codex agent hook 无 marker,
removeAgentHook / reconcileCodexFormat 只能靠 manifest 里记录的 command 匹配。manifest 丢失/损坏时,codex 残留无法被扫除(claude 不受影响)——「no residue」保证对 codex 不成立。
根因(已核实官方 schema)
查证 codex 官方源码 codex-rs/config/src/hook_config.rs(main):
- 单个 hook 条目是内部标签枚举
HookHandlerConfig::Command,字段封闭:command / commandWindows / timeout / async / statusMessage / additionalContextLimit。没有 id / name / description 等自由标识位。
- 顶层
HooksFile 标了 #[serde(deny_unknown_fields)],且条目是 internally-tagged enum —— 往文件/条目里塞自定义键(如 _teamai)会导致 codex 解析报错,不是被忽略。官方文档亦印证:"parser is aware of specific known fields and reacts to unexpected values"。
- 顶层虽有一个
description 元数据字段,但它"doesn't change which hooks run"、不在条目层,无法给单条 hook 打标识。
结论:codex 条目里唯一能承载 [teamai:agent-hook:<slug>] 字符串的字段是 statusMessage —— 而它是"hook 运行时在 UI 显示的提示语",会对用户可见。不存在零副作用的 marker 字段。
候选方案
- A. 用
statusMessage 当 marker(对齐 claude description 机制)
- CodexHookEntry 增加
statusMessage? 字段,写入 [teamai:agent-hook:<slug>];按 marker 精确匹配替换/删除,reconcileCodexFormat 的 removeAll 分支也能按 marker 扫除残留。
- ✅ 取消「唯一 cmd」约束;✅ teardown 不再单靠 manifest(消除不对称)。
- ⚠️ 代价:该 marker 在 codex 运行该 hook 时会于 UI 显示(内容语义上约等于"teamai 托管的 hook"提示)。
- B. 维持现状(command 匹配 + manifest)
- 零 UI 副作用,但保留「唯一 cmd」约束、且 manifest 丢失时 codex 残留扫不掉。
关联
参考
背景
#238(
install_hook_rule/uninstall_hook_rule,实现见 #265)为 codex 落地了 HTTP-source agent hook。claude 格式用条目上的description写入专属 marker[teamai:agent-hook:<slug>],卸载时按 slug 精确匹配。但 codex 的单个 hook 条目没有任何可承载 slug 的标识字段,导致两个问题。问题
1. 「每个 codex slug 必须用唯一
cmd」是一个不友好的对外约束。codex 条目只能靠
command字符串区分。若两个不同 slug 用了相同cmd,卸载其一时会按command === "..."误删另一个。目前只能在文档里要求后端「每个 codex slug 用唯一 cmd」来规避。2. teardown 对 codex 不对称、依赖 manifest 完整性。
[teamai:agent-hook:marker,即使本地 manifest(~/.teamai/local-agent/agent-hooks.json)丢失,reconcileHooks(removeAll)仍能扫描 marker 清除残留。removeAgentHook/reconcileCodexFormat只能靠 manifest 里记录的 command 匹配。manifest 丢失/损坏时,codex 残留无法被扫除(claude 不受影响)——「no residue」保证对 codex 不成立。根因(已核实官方 schema)
查证 codex 官方源码
codex-rs/config/src/hook_config.rs(main):HookHandlerConfig::Command,字段封闭:command/commandWindows/timeout/async/statusMessage/additionalContextLimit。没有id/name/description等自由标识位。HooksFile标了#[serde(deny_unknown_fields)],且条目是 internally-tagged enum —— 往文件/条目里塞自定义键(如_teamai)会导致 codex 解析报错,不是被忽略。官方文档亦印证:"parser is aware of specific known fields and reacts to unexpected values"。description元数据字段,但它"doesn't change which hooks run"、不在条目层,无法给单条 hook 打标识。结论:codex 条目里唯一能承载
[teamai:agent-hook:<slug>]字符串的字段是statusMessage—— 而它是"hook 运行时在 UI 显示的提示语",会对用户可见。不存在零副作用的 marker 字段。候选方案
statusMessage当 marker(对齐 claudedescription机制)statusMessage?字段,写入[teamai:agent-hook:<slug>];按 marker 精确匹配替换/删除,reconcileCodexFormat的 removeAll 分支也能按 marker 扫除残留。关联
CodexHookEntry缺statusMessage/commandWindows/additionalContextLimit等官方字段,属 [codex] support codex hooks injection #103 既有实现,是否补全需单独确认,不在 feat(local-agent): install/uninstall session hooks via sync + ack (#238) #265 处理)参考