今天(2026-08-03)一整天的 v17 协议变更派发里,有六处已经付出过代价 的经验,现行 skill 里没有,或者只写了半截。逐条给出事故、缺口、和要补的规则。
维护者已确认按此更新 skill。
A. pm-dispatch —— 四条主项
A1. ACCEPT 的落地判据只到「进队列」就断了
SKILL.md 第 7 步 ACCEPT 写的是「once every check is green, mark it ready and add it to the merge queue 」,然后就结束了。没有任何一句说怎么确认它真的落地 。
事故 :PR #4852 的 auto-merge 从 10:15 就挂着,我每轮查「git grep origin/main,判据在不在 main 上」,不在 → 记为「还在队列里排队」。实际上它从来没进过队列 —— CI 是红的,auto-merge 永远不会触发。这样空转了 100 分钟 。
根因 :「不在 main 上」这一个读数,无法区分 「在队列里等」和「压根没进队列」。两者的处置完全相反(前者等,后者要动手)。
要补的规则 :落地检查必须是两个读数,不是一个 ——
git ls-remote origin ' refs/heads/gh-readonly-queue/*' # 真的在队列里?
git log --oneline -1 origin/main # 真的落地了?
队列分支名里带 PR 号(gh-readonly-queue/main/pr-4878-<base-sha>),而且基 sha 串成链 —— 能同时读出「排第几」。另外:PR 一旦被转回 draft,auto-merge 与队列成员资格都会掉 ,不会自动恢复。
A2. rerun_failed_jobs 的语义:复用原 run 的合并 ref,不重算
skill 全文没提重跑。但 PM 恰恰是按重跑按钮的那个人。
事故 :#4852 的 CI 红在 #4856 (testTimeout: 60_000)落地之前 。我重跑了,还是红;当时的判断是「队列慢 / flaky」。实际上 rerun_failed_jobs 复用原 run 的提交与合并 ref ,不会拿新的 main 重算 —— 重跑一百次都还是 5000ms 的那个基。
要补的规则 :当红的原因是「基上缺一个已经合并的修复」时,重跑无效,只有推新提交才能带上新 main (git merge origin/main 后 push)。区分方法:先看失败签名,再看那个修复的合并时间与本 run 的创建时间。
与既有的 .github/workflows/rerun-safety-nightly.yml 无关 —— 那个查的是「同一 checkout 里跑两遍是否自洽」的测试污染,不是重跑语义。
A3. 入队前的生成物同步(merge=os-regen 静默吞并)
第 3 步只保证「同一批 内 file-disjoint」。它管不到先后两单都碰 packages/spec 生成物 的情形 —— 而协议变更几乎必然如此。
.gitattributes 把 spec-changes.json / authorable-surface.json / json-schema.manifest.json / api-surface.json / api-surface-signatures.json 路由到 merge=os-regen。这个驱动会让 merge exit 0、零冲突标记 ,却静默丢掉一侧的改动 。只有重新生成才暴露。
今天在三个 PR 上各复现一次 :#4809 、#4846 、#4841 。
要补的规则 (PM 侧,写进 ACCEPT 前的动作):任何碰这五个文件的 PR,入队前要求 dev ——
git merge origin/main(⛔ 禁 rebase / force-push,AGENTS.md §3)
git checkout origin/main -- <五个生成物>
整体重新生成 ,⛔ 绝不做文本合并
断言所有兄弟单的条目都还在 (今天 refactor(spec)!: 退役 plugin-runtime 家族五个 schema —— 无任何 runtime 实现的「Dynamic Loading」词表 (#4834, ADR-0049) #4878 是四条 step17 条目并存)
驱动本身另有缺陷,已单独立 #4868 。
A4. CI 红了怎么诊断 —— 三条读日志的纪律
skill 里没有任何 CI 诊断章节。今天我在这里犯了当天最严重的错误:公开断定四次 CI 红是内核 OOM-killer 杀掉 DTS 构建 ,并据此开了 PR #4853 。被 #4853 自己的 CI 推翻 —— 它挂着 8192 跑,红得一模一样。真因是 #4796 那一族的 5000ms 超时,由 #4856 修掉。完整更正见 #4845 。
三个叠加的错误,每一个都该成为一条规则:
check-test-completeness.mjs 的「all N accounted for」≠「测试通过」。 它只断言没有 worker 静默死掉。workflow 自己的注释:"A red suite plus a GREEN completeness check means real test failures" 。
turbo 并发输出的「相邻」≠「因果」。 test 的 dependsOn 只有 ["^build"](上游),packages/spec 没有 pretest,所以 --concurrency=4 下 spec#build(DTS)与 spec#test 同时跑,GitHub 又给整组打同一个时间戳。DTS Build start 紧接着 ELIFECYCLE 完全可能来自两个无关进程。
下结论前拿完整日志归档,不要只看 tail。 那次的 ~10 KB 尾巴被 gen:schema 的 1675 行清单吃光,真正的失败行根本不在里面。
B. pm-dispatch —— 两条次项
B1. 同类基础设施修复的撞车,现有门禁盖不住
.github/workflows/duplicate-fix-guard.yml 已经存在,它在两个 PR 声明同一个 Fixes #N 时把后开的那个判红(#4588 的产物)。
但它盖不住今天这一例 :PR #4864 (我方)和 PR #4856 (另一车道)修的是同一个基础设施问题 ,却挂在不同的 issue 号 下 —— 门禁看不到任何重复。而且 #4856 先合了 60s,我的 #4864 若合进去会把它降回 30s ,即一次静默回退。
要补的规则 :共享基础设施类修复(CI 配置、超时、构建脚本、门禁)在入队前,要按症状 而不是 issue 号复查 main:git log --oneline origin/main -- <该文件>,确认在飞期间没有别人已经修掉。并明确点出现有门禁的覆盖边界。
B2. 读数纪律 —— 三条各自产出过一个我信了的错读数
cd X && cmd 会短路。 Bash 工具每次调用 cwd 重置;cd /home/user/objectui && git grep ... 在路径不存在时 cd 失败但整条命令继续,于是在当前仓里执行 ,给出假的「objectui 零消费方」。⛔ 跨仓一律 git -C <path> grep。
git grep -c … | wc -l 数的是文件数,不是命中数。 曾据此得出「分支比 main 命中更多」的荒谬结论。
裸名 grep 会被幸存家族当子串命中。 system/EmailTemplate 命中仍然活着的 EmailTemplateDefinition 一族;退役核验必须带引号精确名 。
统一原则:空结果必须用「确定存在的邻近词」反查 ,证伪「扫描器/路径坏了」这个解释,否则零命中不成立。
C. spec-property-retirement —— 一条
C1. 两种退役形态在 ratchet 上的可见性是相反 的
今天各测到一例,可以互为对照:
退役形态
四张 ratchet
实例
枚举值 收窄
字节完全相同 (不可见)
#4391 crypto.hash
整 def 删除
必须变化
#4834 :api-surface −12 / authorable −23 / manifest −5
为什么重要 :验收时「ratchet 零变化」到底是正常还是异常,完全取决于走的是哪条退役路线 。拿错对照就会把正常判成异常(白折腾),或者把异常判成正常(放过一个没真正删掉的 def)。
这条属于退役形态判据,放 spec-property-retirement(第 2 节「the fork: which removal route」附近),pm-dispatch 的验收清单引用即可,不重复。
验收标准
.claude/skills/pm-dispatch/SKILL.md 覆盖 A1–A4、B1–B2,规则写成可执行的命令 / 可核对的判据 ,不是泛泛的告诫;每条带上事故编号,后来者能回溯。
.claude/skills/spec-property-retirement/SKILL.md 覆盖 C1。
B1 必须明确写出 duplicate-fix-guard.yml 的覆盖边界 ,不能让读者以为门禁已经管住了。
⛔ 不碰 content/docs/releases/。
空 frontmatter changeset(本 PR 不发布任何东西)。
今天(2026-08-03)一整天的 v17 协议变更派发里,有六处已经付出过代价的经验,现行 skill 里没有,或者只写了半截。逐条给出事故、缺口、和要补的规则。
维护者已确认按此更新 skill。
A.
pm-dispatch—— 四条主项A1. ACCEPT 的落地判据只到「进队列」就断了
SKILL.md第 7 步 ACCEPT 写的是「once every check is green, mark it ready and add it to the merge queue」,然后就结束了。没有任何一句说怎么确认它真的落地。事故:PR #4852 的 auto-merge 从 10:15 就挂着,我每轮查「
git grep origin/main,判据在不在 main 上」,不在 → 记为「还在队列里排队」。实际上它从来没进过队列 —— CI 是红的,auto-merge 永远不会触发。这样空转了 100 分钟。根因:「不在 main 上」这一个读数,无法区分「在队列里等」和「压根没进队列」。两者的处置完全相反(前者等,后者要动手)。
要补的规则:落地检查必须是两个读数,不是一个 ——
队列分支名里带 PR 号(
gh-readonly-queue/main/pr-4878-<base-sha>),而且基 sha 串成链 —— 能同时读出「排第几」。另外:PR 一旦被转回 draft,auto-merge 与队列成员资格都会掉,不会自动恢复。A2.
rerun_failed_jobs的语义:复用原 run 的合并 ref,不重算skill 全文没提重跑。但 PM 恰恰是按重跑按钮的那个人。
事故:#4852 的 CI 红在 #4856(
testTimeout: 60_000)落地之前。我重跑了,还是红;当时的判断是「队列慢 / flaky」。实际上rerun_failed_jobs复用原 run 的提交与合并 ref,不会拿新的 main 重算 —— 重跑一百次都还是 5000ms 的那个基。要补的规则:当红的原因是「基上缺一个已经合并的修复」时,重跑无效,只有推新提交才能带上新 main(
git merge origin/main后 push)。区分方法:先看失败签名,再看那个修复的合并时间与本 run 的创建时间。A3. 入队前的生成物同步(
merge=os-regen静默吞并)第 3 步只保证「同一批内 file-disjoint」。它管不到先后两单都碰
packages/spec生成物的情形 —— 而协议变更几乎必然如此。.gitattributes把spec-changes.json/authorable-surface.json/json-schema.manifest.json/api-surface.json/api-surface-signatures.json路由到merge=os-regen。这个驱动会让 merge exit 0、零冲突标记,却静默丢掉一侧的改动。只有重新生成才暴露。今天在三个 PR 上各复现一次:#4809、#4846、#4841。
要补的规则(PM 侧,写进 ACCEPT 前的动作):任何碰这五个文件的 PR,入队前要求 dev ——
git merge origin/main(⛔ 禁 rebase / force-push,AGENTS.md §3)git checkout origin/main -- <五个生成物>驱动本身另有缺陷,已单独立 #4868。
A4. CI 红了怎么诊断 —— 三条读日志的纪律
skill 里没有任何 CI 诊断章节。今天我在这里犯了当天最严重的错误:公开断定四次 CI 红是内核 OOM-killer 杀掉 DTS 构建,并据此开了 PR #4853。被 #4853 自己的 CI 推翻 —— 它挂着 8192 跑,红得一模一样。真因是 #4796 那一族的 5000ms 超时,由 #4856 修掉。完整更正见 #4845。
三个叠加的错误,每一个都该成为一条规则:
check-test-completeness.mjs的「all N accounted for」≠「测试通过」。 它只断言没有 worker 静默死掉。workflow 自己的注释:"A red suite plus a GREEN completeness check means real test failures"。test的dependsOn只有["^build"](上游),packages/spec没有pretest,所以--concurrency=4下spec#build(DTS)与spec#test同时跑,GitHub 又给整组打同一个时间戳。DTS Build start紧接着ELIFECYCLE完全可能来自两个无关进程。gen:schema的 1675 行清单吃光,真正的失败行根本不在里面。B.
pm-dispatch—— 两条次项B1. 同类基础设施修复的撞车,现有门禁盖不住
.github/workflows/duplicate-fix-guard.yml已经存在,它在两个 PR 声明同一个Fixes #N时把后开的那个判红(#4588 的产物)。但它盖不住今天这一例:PR #4864(我方)和 PR #4856(另一车道)修的是同一个基础设施问题,却挂在不同的 issue 号下 —— 门禁看不到任何重复。而且 #4856 先合了 60s,我的 #4864 若合进去会把它降回 30s,即一次静默回退。
要补的规则:共享基础设施类修复(CI 配置、超时、构建脚本、门禁)在入队前,要按症状而不是 issue 号复查 main:
git log --oneline origin/main -- <该文件>,确认在飞期间没有别人已经修掉。并明确点出现有门禁的覆盖边界。B2. 读数纪律 —— 三条各自产出过一个我信了的错读数
cd X && cmd会短路。 Bash 工具每次调用 cwd 重置;cd /home/user/objectui && git grep ...在路径不存在时cd失败但整条命令继续,于是在当前仓里执行,给出假的「objectui 零消费方」。⛔ 跨仓一律git -C <path> grep。git grep -c … | wc -l数的是文件数,不是命中数。 曾据此得出「分支比 main 命中更多」的荒谬结论。system/EmailTemplate命中仍然活着的EmailTemplateDefinition一族;退役核验必须带引号精确名。C.
spec-property-retirement—— 一条C1. 两种退役形态在 ratchet 上的可见性是相反的
今天各测到一例,可以互为对照:
crypto.hashapi-surface −12/authorable −23/manifest −5为什么重要:验收时「ratchet 零变化」到底是正常还是异常,完全取决于走的是哪条退役路线。拿错对照就会把正常判成异常(白折腾),或者把异常判成正常(放过一个没真正删掉的 def)。
这条属于退役形态判据,放
spec-property-retirement(第 2 节「the fork: which removal route」附近),pm-dispatch的验收清单引用即可,不重复。验收标准
.claude/skills/pm-dispatch/SKILL.md覆盖 A1–A4、B1–B2,规则写成可执行的命令 / 可核对的判据,不是泛泛的告诫;每条带上事故编号,后来者能回溯。.claude/skills/spec-property-retirement/SKILL.md覆盖 C1。duplicate-fix-guard.yml的覆盖边界,不能让读者以为门禁已经管住了。content/docs/releases/。