get-shit-done 修复 3346 深度解析:Codex AoT Hooks 迁移中 TOML 叶子键必须取事件名而非位置元组 📅 发布时间:2026/9/7 17:53:48 👁 浏览次数: get-shit-done 修复 #3346 深度解析Codex AoT Hooks 迁移中 TOML 叶子键必须取事件名而非位置元组【免费下载链接】get-shit-doneA light-weight and powerful meta-prompting, context engineering and spec-driven development system for Claude Code by TÂCHES.项目地址: https://gitcode.com/GitHub_Trending/getshi/get-shit-done本文基于 get-shit-done 仓库中的 changeset 记录 .changeset/3346-codex-aot-toml-key.md 展开完整还原 #3346 这个 bug 的故障现场在 Windows 上为老版本 Codex 配置做 hooks 格式迁移时migrateCodexHooksMapFormat把位置元组原样当作了 TOML 叶子键导致 Codex 0.124.0 拒绝加载配置、安装中断。结合 bin/install.js 中的迁移实现、tests/bug-3346-codex-aot-toml-key.test.cjs 回归测试以及后置 schema 校验链路本文会讲清楚修复的判定规则正文event ...优先作为叶子键、迁移后目标配置的两级嵌套结构以及“迁移 → 校验 → 原子写入 → 失败回滚”这一整条安装管线的安全设计帮助维护多运行时Claude Code / Codex 等配置迁移逻辑的开发者理解如何安全地做 TOML 格式升级。1. 问题背景Codex hooks 配置从 map 格式迁移到 AoT 格式changeset 记录的核心事实是Codex 0.124.0 把 hooks 配置从旧的 map 风格[hooks.X]表格键 处理器字段改成了新的 array-of-tablesAoT格式。这一点可以从 bin/install.js 中migrateCodexHooksMapFormat函数的头注直接读到Codex 0.124.0 changed from the old map-style hooks config: [hooks] [hooks.shell] command ... to the new array-of-tables format.对于 get-shit-done 这样的多运行时安装器安装/更新时npx get-shit-done-cclatest会在 Codex 目标目录下读取config.toml把用户机器上遗留的旧格式 hooks 段自动迁移到新版结构否则新版 Codex CLI 直接拒绝加载整个配置文件。changeset 中提到的用户可见症状即在“早于 AoT 迁移时期”的 Windows 配置上这条迁移路径会产出非法 TOML最终导致 Codex 运行时安装中止。而 #3346 要回答的更细一层的问题是迁移时新的[[hooks.EVENT]]头部里EVENT这一段叶子键到底应该取什么2. 故障现场位置元组被原样当作叶子键老的 Codex 版本在写[hooks.X]段时表格键X并不总是事件名有时是一个file:event:line:col形式的位置标识符diagnostic location identifier真正的 eventName 放在段正文的event ...字段里。changeset 给出的真实故障样本是[hooks.C:\Users\helen\.codex\config.toml:session_start:0:0] event session_start command echo hi修复前的迁移逻辑直接把[hooks.X]的路径段X原样verbatim作为新 AoT 块的叶子键重新输出于是生成了如下头部[[hooks.C:\Users\helen\.codex\config.toml:session_start:0:0]]这个键链对 Codex 0.124.0 而言不是合法的事件名叶子键Codex 会拒绝加载该配置——“叶子键段应该是事件名而不是诊断位置标识符”tests/bug-3346-codex-aot-toml-key.test.cjs 头注中的原话。由于 Windows 用户的老配置更容易出现这种带完整文件路径的表格键C:\Users\...该问题在 Windows 上集中爆发。3. 迁移器实现三类遗留形态的统一识别migrateCodexHooksMapFormat 的职责是检测配置中所有不符合新版形态的 hooks 段并转换。从源码结构看它通过getTomlTableSections把整个 TOML 切成表格段然后识别三类需要迁移的遗留形态map 格式段bin/install.js#L3673-L3679裸[hooks]容器段以及恰好两段路径、非数组的[hooks.TYPE]事件表格。这里有两个关键排除规则用section.segments.length 2真实解析出的键段数而不是对section.path做startsWith或按.分割判断避免把[hooks.SessionStart.hooks]这类三段嵌套处理器表误识别为名为SessionStart.hooks的事件也避免把带引号且含点号的键如[[hooks.before.tool]]误判排除hooks.state与hooks.state.*——这是 Codex CLI 0.130.0 的持久化 hook 信任命名空间永远使用普通表格形态绝不使用 AoT。扁平 AoT 段bin/install.js#L3686-L3688path hooks且为数组的[[hooks]]条目。扁平[[hooks]]与命名空间[[hooks.EVENT]]不能在同一文件共存hooks不可能同时是数组又是表因此每个扁平条目要按其event键迁移为[[hooks.EVENT]]。过期命名空间 AoT 段bin/install.js#L3696-L3712[[hooks.TYPE]]条目在事件条目层级直接携带处理器字段command、type、timeout、statusMessage见STALE_HANDLER_FIELD_PATTERN但没有嵌套的[[hooks.TYPE.hooks]]子表——这是 #2773 之前、Codex 0.124.0 拒绝的单块形态需要提升为两级嵌套形态。已经带.hooks子表的条目不动只有 matcher 字段、没有处理器字段的条目是合法形态有意跳过。三类段都为空时函数直接原样返回内容不做任何改动。4. 修复核心正文event ...优先决定叶子键#3346 的修复集中在 map 格式分支的重新输出逻辑bin/install.js#L3811-L3824源码注释直接标注了#3346// #3346: when the legacy [hooks.X] body declares event ..., // prefer that as the event-name leaf key. The path segment X may be // a file:event:line:col location identifier (Codex pre-AoT // wrote those as table keys), which is not a valid leaf event name — // emitting it verbatim produces a TOML key chain Codex 0.124.0 rejects. const bodyEvent extractFlatHookEventName(body); const type bodyEvent ! null ? bodyEvent : s.path.slice(hooks..length); const skipKeys bodyEvent ! null ? new Set([event]) : new Set(); return buildNestedBlock(type, body, skipKeys);判定规则可以归纳为一条如果段正文声明了event ...该事件名胜出作为叶子键否则回退到原路径段。并且当事件名来自正文时event字段被加入skipKeys从重新输出的处理器正文中剔除——事件名已经被“提升”到了头部键里处理器体里再保留一份event属于冗余遗留字段。changeset 的结论句与此一致“map 格式分支与过期命名空间 AoT 分支现在镜像了扁平 AoT 分支”的做法。过期 AoT 分支的对应位置在 bin/install.js#L3831-L3836同样是bodyEvent ! null ? bodyEvent : 路径段的取值。事件名的提取由 extractFlatHookEventName 完成其严谨性体现在同时接受 TOML 双引号含转义与单引号字符串显式拒绝空事件名event 或event ——无法做有意义的命名空间化条目保持原样不动。叶子键在写入头部前还要经过 tomlBareKey 的引号处理bare key 只允许[A-Za-z0-9_-]含空格、点号等字符的事件名例如Before Tool必须包成双引号 TOML 字符串并对反斜杠与双引号做转义否则会产生非法 TOML。5. 迁移目标形态两级嵌套 AoT 与处理器字段归属buildNestedBlockbin/install.js#L3763-L3776负责生成迁移后的目标结构。它对段正文按字段分层只有matcher属于事件层级其余字段都属于处理器层级parseHooksBodybin/install.js#L3726-L3751字段解析使用parseTomlKey而非旧的/^([\w.])\s*/正则使连字符键如status-message与引号键也能被正确识别——旧正则会静默丢弃这些键。以故障样本为例修复后迁移输出为[[hooks.session_start]] [[hooks.session_start.hooks]] type command command echo hi几个值得注意的实现细节若处理器字段为空例如仅含matcher的过滤条目不会合成一个空的[[hooks.TYPE.hooks]]块——那样是结构合法但语义损坏的输出没有command的处理器条目处理器条目缺少显式type时默认补上type commandhasExplicitType机制但不会重复添加已有的type字段插入位置有讲究map 格式块与过期命名空间块插入在“第一个剩余表格段之前”以保留其在文件中的相对位置而扁平 AoT 块只能追加到文件末尾因为 AoT 在 TOML 中不可能先于普通表格出现插到普通表格之前会破坏[features]/[model]等的相对顺序bin/install.js#L3805-L3810。6. 回归测试三个场景锁死修复边界tests/bug-3346-codex-aot-toml-key.test.cjs 用三个用例完整覆盖了修复的边界。值得注意的是其测试纪律文件头注明确要求用项目自己的parseTomlToObject解析迁移后的 TOML对解析出的对象形状做断言而不是 grep 原始字符串——这避免了“文本看起来对但结构不对”的假阳性。用例 1位置元组键 event字段#3346 核心断言const legacy [ [hooks.C:\\\\Users\\\\helen\\\\.codex\\\\config.toml:session_start:0:0], event session_start, command echo hi, , ].join(\n); const migrated migrateCodexHooksMapFormat(legacy); const parsed parseTomlToObject(migrated); // 核心断言hooks 必须只以事件名为键 assert.deepEqual(Object.keys(parsed.hooks), [session_start]);并对两级嵌套结构逐项验证hooks.session_start[0].hooks[0].command echo hi、type默认补齐为command以及handlers[0].event undefined——处理器体不得保留遗留的event字段。用例 2显式type 位置元组键——验证显式type command不会被迁移器重复输出且叶子键仍取自正文event此处事件为tool_call_pre。用例 3回归护栏——标准遗留形态[hooks.session_start]表格键即事件名、正文无event字段在修复后必须继续按原路径段迁移确保修复没有破坏原本就正确的分支。7. 纵深防御迁移失败如何被校验与回滚机制兜住单看 #3346问题出在“迁移器输出了非法叶子键”。但从源码结构看get-shit-done 在 Codex 安装管线中为此类错误布置了多层防线理解这些层能说明为什么这类 bug 的影响面被限制在“安装中止 配置回滚”而不是“用户拿到一个坏掉的 Codex”。1迁移时机先剥离 GSD 托管块再迁移用户块。安装流程在 bin/install.js#L9035-L9056 中先执行stripStaleGsdHookBlocks按 Shape 1/2/3/4 顺序剥离历史遗留的 GSD 托管 hook 块然后才调用migrateCodexHooksMapFormat确保迁移只触碰用户自己写的 hooks——顺序颠倒会让 GSD 自身的过期块先被迁移导致后续剥离正则失配#2698 的历史教训。2写前 schema 校验。即将写入的字节会先交给 validateCodexConfigSchema 解析验证。从源码结构看它明确拒绝的形态包括扁平[[hooks]]数组表Codex 0.124.0 要求[[hooks.Event]]命名空间形态裸[hooks.Event]单括号表事件处理路径必须是 AoT[[agents]]序列形态与裸[agents]表Codex 要求[agents.name]结构形态hooks.state.*使用 AoT 形态该命名空间必须是普通表格事件条目层级出现游离的处理器字段command/type/timeout/statusMessage而无.hooks子表——注释明确说迁移器应当先转换掉这些形态“如果还出现在这里说明迁移没覆盖到宁可靠校验失败也不放行坏配置”bin/install.js#L4385-L4405处理器type只能为command。3校验/写入失败即回滚并中止。校验不通过时恢复安装前快照并抛出致命错误bin/install.js#L9067-L9083写入采用“写同级临时文件再renameSync覆盖”的原子方式中途失败不会截断现有配置bin/install.js#L9085-L9102pre-write阶段失败包括迁移函数本身抛错同样被 #2760 CR5 提升为致命错误并触发快照恢复避免降级为 warn 后继续打印 “Done!”。#3346 修复前Windows 老配置触发的正是这条“生成非法键 → 校验/加载失败 → 安装中止”的路径修复后该路径不再被触发。4与用户既有形态的协同。hasUserNamespacedAotHooksbin/install.js#L3897-L3899检测用户是否已在使用[[hooks.EVENT]]形态若是GSD 托管的 hook 块必须输出同样的形状避免扁平与命名空间两种 AoT 混用导致 round-trip 写器产生 Codex 拒绝的配置#2760, defect 3。8. 发布记录与实战影响该修复随 v1.42.1 发布。docs/RELEASE-v1.42.1.md 的 Fixed 一节将其归纳为“Codex install and hook migration are safer— AoT hooks use event-name leaf keys, duplicate legacyhooks.jsonentries are removed, user hooks are preserved, and unsupported execute-phase worktrees are blocked”并关联了 #3346。对使用者的实际意义是适用前提你使用 Codex 运行时且config.toml中存在早于 AoT 迁移时期的旧格式[hooks.X]段尤其 Windows 上键为完整文件路径的位置元组形态修复前npx get-shit-done-cclatest在迁移时生成[[hooks.path:event:line:col]]这类非法头部Codex 0.124.0 拒绝加载安装中止修复后迁移器优先读取正文event ...作为事件名叶子键并剔除冗余字段老配置被正确转换为两级嵌套 AoT安装流程可顺利完成排查建议若怀疑本机 Codex 配置处于非法形态可关注安装输出中的Migrated legacy Codex [hooks] format to two-level nested AoT提示行bin/install.js#L9055并确认最终配置中事件处理段均为[[hooks.EVENT]][[hooks.EVENT.hooks]]两级结构、hooks.state如有为普通表格。9. 要点小结叶子键取值规则[hooks.X]迁移为[[hooks.EVENT]]时正文event ...优先于路径段X路径段可能是file:event:line:col位置元组绝不能作为叶子键原样输出.changeset/3346-codex-aot-toml-key.md、bin/install.js#L3815-L3823。三条分支同一规则map 格式分支、过期命名空间 AoT 分支与既有扁平 AoT 分支采用相同的“正文事件名胜出 event字段剔除”策略避免同一文件内形态不一致。形态细节不能省非 bare-key 事件名要加引号转义、无type时补type command、纯 matcher 条目不合成空处理器块、hooks.state永远排除在 AoT 迁移之外。验证方式决定可信度回归测试用parseTomlToObject断言解析后的对象形状而非字符串匹配三个用例分别覆盖核心修复、显式type场景与标准遗留形态的回归护栏tests/bug-3346-codex-aot-toml-key.test.cjs。管线级兜底剥离 → 迁移 → 写前 schema 校验 → 原子写入 → 失败恢复快照的中止策略把“迁移器写坏配置”这一类错误的最终影响收敛为“本次安装中止且配置还原”而不是交付一个 Codex 无法加载的坏文件。【免费下载链接】get-shit-doneA light-weight and powerful meta-prompting, context engineering and spec-driven development system for Claude Code by TÂCHES.项目地址: https://gitcode.com/GitHub_Trending/getshi/get-shit-done创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考