☰
gsd-core 修复解析:get-shit-done-cc --codex 不再拒绝 Codex 0.130.0+ 的 hooks.state 信任持久化表
2026/9/25 13:19:31 网站建设 项目流程

【免费下载链接】gsd-core

Git. Ship. Done - Core

项目地址:https://gitcode.com/gh_mirrors/ge/gsd-core
点击查看免费下载

本文基于 gsd-core(Git. Ship. Done - Core)仓库中的变更档案 .changeset/archived/mellow-lynx-forage.md(PR 3289,类型 Fixed)展开,讲清一个真实的安装器缺陷:schema 校验器曾把 Codexconfig.toml中所有hooks.*表一律按"事件处理器的数组表(array-of-tables,下称 AoT)"来分类,导致 Codex CLI 0.130.0 起引入的hooks.state.<project>/...每钩子信任状态表被误判为非法、安装被拒。读完本篇,你将理解该误判的根因、修复在 bin/install.js 中的具体落点(迁移与校验两条链路的 carve-out)、以及 tests/codex-config-hooks.test.cjs 中用于锁定该行为的回归用例矩阵,从而能够在遇到 Codex 配置文件校验报错时自行定位是"事件表形状错误"还是"hooks.state 命名空间误伤"。

1. 变更档案说了什么

变更档案原文(.changeset/archived/mellow-lynx-forage.md)只有简短的 front matter 和一句话描述,front matter 标记type: Fixed、pr: 3289,正文要点是:

get-shit-done-cc --codexno longer rejects valid Codexhooks.statetrust-persistence entries — the schema validator was over-classifying everyhooks.*table as an event-handler array-of-tables, breaking installs against Codex CLI 0.130.0+ wherehooks.state.<project>/...stores per-hook trust state. Regular-table shape is now accepted forhooks.state.*whilehooks.<EVENT>still requires AoT.

把它拆解成三个可验证的事实断言:

  1. 症状:执行--codex安装时,校验器拒绝(rejects)合法的hooks.state信任持久化条目,导致安装中断;
  2. 根因:schema 校验器"过度分类"——把每一个hooks.*表都当作事件处理器的 AoT 来处理;
  3. 修复语义:hooks.state.*命名空间接受常规表(regular table)形状,而hooks.<EVENT>(真正的钩子事件表)仍然要求AoT 形状,二者规则分轨。

下面结合源码逐条印证这三点,并补全"0.124.0 格式变更 → 0.130.0 新增命名空间 → 校验器踩坑"的完整时间线。

2. 背景:Codex config.toml 的钩子格式演进,与 gsd-core 为什么要在意它

gsd-core 安装到 Codex 运行时时会写入并维护用户的config.toml。在安装器源码 bin/install.js 中,钩子配置的格式演进有明确注释记录(bin/install.js#L5483-L5492):

  • Codex 0.124.0 之前:map 风格的[hooks]容器 +[hooks.<TYPE>]子表;
  • Codex 0.124.0 起:改为 AoT(array-of-tables)风格,即[[hooks.<TYPE>]],且事件表内部的处理程序字段(command、type、timeout、statusMessage)必须嵌套在[[hooks.<TYPE>.hooks]]子表中;
  • Codex 0.130.0 起:新增hooks.state命名空间用于持久化"每个钩子的信任状态"(per-hook trust state),形状是常规表,例如子键是带引号的项目/钩子路径字符串。

安装器对应地实现了三个关键纯函数,均位于 bin/install.js:

函数位置职责
migrateCodexHooksMapFormatbin/install.js#L5503把旧的 map 风格 / 扁平[[hooks]]/ 单层[[hooks.<TYPE>]]形状迁移到当前[[hooks.<EVENT>]]AoT 形式,保留键值对与注释
validateCodexConfigSchemabin/install.js#L6158安装前/后的结构校验:解析 TOML + 扫描表头,按 Codex 当前 schema 规则逐条放行或拒绝
stripStaleGsdHookBlocksbin/install.js#L5386用 TOML AST 结构性地剥离 GSD 自管的过期钩子块(按command值识别,而非正则)

缺陷正发生在validateCodexConfigSchema与migrateCodexHooksMapFormat共同依赖的那条粗粒度规则上:"凡是hooks.*路径的表,都应按钩子事件表对待"。

3. 缺陷复盘:一个合法的 0.130.0+ 配置为什么会安装失败

Codex CLI 0.130.0+ 的用户config.toml中合法地存在这样的结构(键名形状取自仓库回归测试的真实 fixture,见 tests/codex-config-hooks.test.cjs#L2529-L2542):

[features] hooks = true [[hooks.SessionStart]] matcher = "" [[hooks.SessionStart.hooks]] command = "node gsd-check-update.js" [hooks.state] [hooks.state.'/home/user/.codex/hooks.json:pre_tool_use:0:0'] # Codex 写入的每钩子信任持久化字段

注意最后一节:[hooks.state.'...']是单括号常规表,且子键是一个包含路径、冒号、点号的带引号 key。在修复前,validateCodexConfigSchema的表头检查对一切以hooks.开头的非数组表统一执行"事件表必须是 AoT"的拒绝逻辑,于是:

  • 裸的[hooks.state]容器 → 被当作"非法的单括号[hooks.<Event>]"拒绝;
  • [hooks.state.'<key>']→ 同样被拒绝。

安装流程因此中断,报错信息形如bare [hooks.state] table is invalid in current Codex schema (expected [[hooks.state]] array-of-tables)——错误提示反而建议用户把信任状态表改写成 AoT,方向完全相反。这正是变更档案所说的 "over-classifying everyhooks.*table as an event-handler array-of-tables"。

还有一个容易忽视的次生风险:若不做 carve-out,migrateCodexHooksMapFormat会在迁移阶段把hooks.state.*这些常规表改写成[[hooks.state.*]]AoT 形式——不但不修复,还会把合法配置主动写成 Codex 拒绝的形状。因此修复必须同时落在"迁移"与"校验"两条链路上,源码中这两处都有对应的排除逻辑(见下节)。

4. 修复落点(一):迁移函数排除 hooks.state 命名空间

migrateCodexHooksMapFormat收集"遗留 map 风格节"的过滤器,在修复后显式排除hooks.state与其全部子键(bin/install.js#L5510-L5517):

// Exclude hooks.state and hooks.state.* — these are Codex's persistent hook-trust // namespace (Codex CLI 0.130.0+) and use regular-table shape, never AoT. const legacyMapSections = sections.filter( (section) => !section.array && ( section.path === 'hooks' || (section.path.startsWith('hooks.') && section.segments.length === 2 && section.path !== 'hooks.state' && !section.path.startsWith('hooks.state.')) ) );

两处细节值得注意:

  1. section.segments.length === 2而非单纯的前缀匹配。getTomlTableSections(bin/install.js#L5118-L5133)保留了表头真实解析出的 key 段数,其注释明确说明动机:要区分 2 段路径(如带引号的hooks."before.tool",key 本身含点号)与 3 段路径(如hooks.SessionStart.hooks),而不能对section.path做按点号 split——否则带引号、内含点号的事件名会被错误分级。hooks.state的排除同样依赖这个准确段数,保证[hooks.state.<key>](2 段:hooks+ 引号 key)被正确识别。
  2. !section.path.startsWith('hooks.state.')覆盖任意深度子键,hooks.state本身再用section.path !== 'hooks.state'精确排除,两者合起来封住整个命名空间。

效果:迁移阶段对hooks.state.*一律"不看见",只处理真正的遗留事件表;用户注释与信任状态字段原样保留。

5. 修复落点(二):校验器的分轨规则

validateCodexConfigSchema(bin/install.js#L6158)采用"表头形状检查 + 解析对象结构确认"双通道。修复后的分轨规则完整表述如下:

通道一:表头检查(bin/install.js#L6203-L6221)

// hooks.state.* is Codex's persistent hook-trust namespace (added in // Codex CLI 0.130.0). It uses regular-table shape, NOT array-of-tables. // [[hooks.state]] or [[hooks.state.<key>]] (AoT) is invalid; reject it. if (section.array && (section.path === 'hooks.state' || section.path.startsWith('hooks.state.'))) { return { ok: false, reason: `[[${section.path}]] is invalid; hooks.state namespace must use regular tables` }; } // All other hooks.* paths (event handlers like hooks.SessionStart) require // AoT shape — bare [hooks.<Event>] (single-bracket) is invalid. if (!section.array && section.path.startsWith('hooks.') && section.path !== 'hooks.state' && !section.path.startsWith('hooks.state.')) { return { ok: false, reason: `bare [${section.path}] table is invalid in current Codex schema (expected [[${section.path}]] array-of-tables)` }; }

即一张按路径前缀分轨的规则表:

表头形状路径修复前修复后
[[hooks.state]]/[[hooks.state.<key>]](AoT)hooks.state*通过(误放行错误形状)拒绝:hooks.state namespace must use regular tables
[hooks.state]/[hooks.state.<key>](常规表)hooks.state*拒绝(误伤,本 bug)放行
[hooks.<Event>](单括号)其他hooks.*拒绝仍拒绝:事件表必须 AoT
[[hooks.<Event>]](AoT)其他hooks.*通过仍通过

通道二:解析对象结构确认(bin/install.js#L6227-L6261)

仅靠表头还不够——同一个解析后形状可能来自不同表头写法(如[agents]+default = "x"与[agents.foo]解析结果无法仅凭对象区分,源码注释对此有专门说明),因此校验器对解析出的parsed.hooks对象再做结构确认:

if (event === 'state') { if (Array.isArray(value)) { return { ok: false, reason: `hooks.state must be a regular table/object, got array-of-tables` }; } if (typeof value !== 'object' || value === null) { return { ok: false, reason: `hooks.state must be a regular table/object, got ${typeof value}` }; } continue; } // 其余 hooks.<Event> 必须仍是数组(AoT) if (!Array.isArray(value)) { return { ok: false, reason: `hooks.${event} must be an array of tables, got ${typeof value}` }; }

关键在event === 'state'分支:解析后hooks.state是一个普通对象(因为它是常规表而非 AoT),如果不在这里continue跳过,就会被下面"其他hooks.<Event>必须是数组"的循环误拒——这正是测试用例hooks.state object in parsed structure does not trigger non-array rejection(tests/codex-config-hooks.test.cjs#L2585)锁定的点。

此外,[[hooks.<Event>]]的 AoT 语义校验并未放松:每个事件条目要么只含matcher等事件级键,要么必须携带[[hooks.<Event>.hooks]]处理器子数组;处理器字段(command/type/timeout/statusMessage)出现在事件条目顶层即拒绝(bin/install.js#L6262-L6289)。这保证了修复是"精准分轨"而非"整体放宽"。

6. 回归测试矩阵:#3285 用例组如何锁定修复

回归用例集中在 tests/codex-config-hooks.test.cjs 的#3285标记用例组(tests/codex-config-hooks.test.cjs#L2518),标题直白:validateCodexConfigSchema: hooks.state is a regular table (not AoT)。核心断言覆盖:

用例输入形状期望
裸[hooks.state]表头2 段常规表通过
[hooks.state.<project-key>]带引号键的常规表子键(如[hooks.state.'/home/user/.codex/hooks.json:pre_tool_use:0:0'])通过
hooks.state与[[hooks.SessionStart]]AoT 共存混合形状两者都通过(分轨不互相干扰)
解析结构中hooks.state为对象通道二不触发 "must be an array" 拒绝
多个hooks.state.<key>子键(多项目)[hooks.state.'/project/a/...']、[hooks.state.'/project/b/...']全部通过
[[hooks.state]]AoT 根拒绝,且 reason 提及hooks.state
[[hooks.state.foo]]AoT 子键拒绝,且 reason 提及hooks.state

另有端到端安装用例组#3285 — install succeeds when config.toml contains hooks.state entries(tests/codex-config-hooks.test.cjs#L2657):在临时安装目录写入含hooks.state信任条目的config.toml后执行安装,断言安装不抛异常——直接对应变更档案中 "breaking installs" 的用户症状。

仓库是只读的,你可以只读地查看上述文件,或按仓库自身测试入口运行对应用例来复现验证(例如以 Node 执行tests/codex-config-hooks.test.cjs,具体方式以仓库根 package.json 的 test 脚本与 TESTING-STANDARDS.md 为准)。

7. 给使用者的自查清单:你的 config.toml 现在应该长什么样

结合修复后的校验规则,Codex 0.130.0+ 环境下 GSD 安装路径能接受的钩子区段形状可以归纳为:

# 事件钩子:必须是 AoT,处理器字段必须嵌套在 .hooks 子表 [[hooks.SessionStart]] matcher = "" [[hooks.SessionStart.hooks]] command = "node gsd-check-update.js" # 信任持久化:必须是常规表(单括号),子键为带引号的路径字符串 [hooks.state] [hooks.state.'/home/user/.codex/hooks.json:pre_tool_use:0:0'] trusted = true # 字段内容以 Codex 实际写入为准,此处仅示意形状

对照检查时的判断口诀:

  1. 看到[[hooks.state开头 →形状错误,GSD 校验会明确报hooks.state namespace must use regular tables,需把双括号改回单括号常规表;
  2. 看到[hooks.SessionStart](单括号)→形状错误,报expected [[hooks.SessionStart]] array-of-tables,需改为 AoT;
  3. 看到hooks.<Event>条目顶层挂着command = ...而没有[[hooks.<Event>.hooks]]→ 这是 0.124.0 前被 Codex 拒绝的"单层形状",migrateCodexHooksMapFormat会在安装时将其提升为两级嵌套形式(bin/install.js#L5529-L5551);
  4. 事件名含点号/空格时,表头 key 必须是带引号的 TOML 字符串(如hooks."before.tool"),这也是getTomlTableSections坚持用解析段数而非字符串 split 的原因。

8. 小结

这次修复(PR 3289,回归组 #3285)的技术实质是:在hooks.*命名空间内部为hooks.state开了一个双向 carve-out——迁移链路不碰它(bin/install.js#L5510-L5517),校验链路对它单独分轨:常规表放行、AoT 与标量形状拒绝(bin/install.js#L6203-L6261)。它没有放松hooks.<EVENT>的 AoT 强制,也没有把校验退化为只看表头,而是保持了"表头形状 + 解析对象结构"双通道确认。对维护者而言,该用例组(tests/codex-config-hooks.test.cjs)是理解 gsd-core 如何处理"上游 CLI 格式演进 vs 本地 schema 校验"这一类问题的良好范本:每次 Codex 引入新命名空间,正确动作是精准分轨 + 全形状矩阵的拒绝/放行断言,而不是放宽前缀规则。

关联文件索引:变更档案 .changeset/archived/mellow-lynx-forage.md;安装器实现 bin/install.js(migrateCodexHooksMapFormat@ L5503、getTomlTableSections@ L5118、stripStaleGsdHookBlocks@ L5386、validateCodexConfigSchema@ L6158、installCodexConfig@ L7074);回归测试 tests/codex-config-hooks.test.cjs。

【免费下载链接】gsd-core

Git. Ship. Done - Core

项目地址:https://gitcode.com/gh_mirrors/ge/gsd-core
点击查看免费下载

相关推荐

上一篇:Palworld存档工具深度解析:技术架构与高级应用实战指南
下一篇:Palworld存档编辑器终极指南:5分钟掌握游戏数据可视化修改

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询