Skip to content

fix(ci): 无法解析的 diff 基准改为落成 output,由已认两次标签读取的步骤裁决 (#6434) - #6508

Merged
hotlong merged 1 commit into
mainfrom
claude/issue-6434-resolve-diff-base-settlement
Aug 8, 2026
Merged

fix(ci): 无法解析的 diff 基准改为落成 output,由已认两次标签读取的步骤裁决 (#6434)#6508
hotlong merged 1 commit into
mainfrom
claude/issue-6434-resolve-diff-base-settlement

Conversation

@hotlong

@hotlong hotlong commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

Fixes #6434

changeset-check 中能让 PR 变红的步骤共五个。PR #6429#6378)让其中四个同时认两次活标签读取;第五个 Resolve the diff base 只带快路径守卫却自带两处 exit 1,跑在结算读之前,因此标签晚到叠加 git 基准真的失败时会红一个本该豁免的 PR。本 PR 采路线 3:该步骤只报告不裁决,裁决下移到一个已认两次读取的步骤。

前提自核(自测,不引用分诊结论)

分诊 2026-08-07 21:58Z 说 .github/workflows/pr-automation.yml 最后一次改动是 35353bd,此后无人动过。本 PR 未引用该结论,而是自己重测,结论一致:

$ git log -1 --format='%H %ci %s' origin/main -- .github/workflows/pr-automation.yml
35353bd7fb2a0b81a06a70d4d22dd3442969b49a 2026-08-07 18:48:35 +0000
fix(ci): Check Changeset 的 skip-changeset 判定加一次「结算读」,首跑不再结构性必红 (#6378) (#6429)

按「跑 check-*.mjs 或含非注释 exit 1」逐步扫描 origin/main1fe436d)上的 job,五步 / 四步带结算读的划分属实

步骤 exit 1 check-*.mjs 快路径读 结算读
Resolve the diff base ✅ ×2
Require a changeset
Reject an empty-frontmatter changeset
Require an ADR-0087 disposition
Guard against accidental major bumps

改动后同一扫描得到 5/5 全部带两次读取。

路线选择

采路线 3。 路线 1(只补注释)被否:意图已记在 PR #6429 正文,搬一句话不解决问题。路线 2 未采用,也未测量 —— 它与 #6378 的成本结论直接冲突,本 PR 不碰结算读的位置(见下)。

改了什么

  1. Resolve the diff base 的两处 exit 1 → 写 base_error output + exit 0;发现点仍发 ::warning::,使基础设施抖动在豁免路径上也留痕(否则无人会提它)。
  2. 新增 Require a usable diff base (adjudicated after the label window):同时认两次读取 + base_error != ''exit 1
  3. 计数步骤加 base_error == '' 守卫 —— 这是正确性而非成本:空 $MERGE_BASEgit diff 失败在管道中段,末端 tr 仍返回 0,该步会报出伪造的 added=0,把判决引向「你忘了写 changeset」这个完全错误的错误信息。
  4. 三个工具链步骤(Setup Node / Corepack / Install)同加该守卫 —— 成本与可读性:该路径上唯一的红只会是裁决步骤,不会是恰好在同一段抖动网络上失败的 install。
  5. 结算读的条件加一个析取项 || base_error != ''

为什么第 5 条不是路线 2:结算读的位置没动(仍在计数之后)。#6429 的成本论证是「等待只向将红的 PR 收取」,而基准不可用同样是将红 —— 且计数步骤在该路径上被跳过,added'',第一个析取项无法为它发声。有可解析基准且写了 changeset 的 PR 仍完全跳过该步,零等待零 API 调用。没有任何 PR 因此新增等待。

改动后的失败签名

供未来读者比对真实事故:

  • job 日志中 Resolve the diff base 绿色,但带 ::warning::Could not compute merge-base(origin/main, HEAD) ... Adjudicated below, once the skip-changeset window has settled.
  • Setup Node.js / Enable Corepack / Install dependencies / Count the changesets this PR adds 四步全部 skipped
  • Settle the skip-changeset window 运行(这是修复的关键:它是唯一看得到结算后标签状态的地方)。
  • 之后二选一:
    • 标签落地 → Require a usable diff base skipped,四个判定步骤全部 skipped,job 绿(本 PR 修的就是这一格);
    • 标签始终没来 → Require a usable diff base ,annotation 以 ::error::Could not compute merge-base(...) 开头并附「两次活标签读取都没找到 skip-changeset」的说明,exit 1

若看到 Resolve the diff base 自己红,说明本改动被回退了。

执行 vs 推理 —— 这是 workflow 文件,分清楚

执行(有真实输出):

  • YAML 解析 + 逐步打印折叠后的 if: 表达式(>- 折叠正确,括号析取正确)。
  • case matrix 模拟器:把文件里真实的 if: 表达式机械翻译成 JS,按 GitHub 的步骤门控语义(无状态函数的 if: 隐含 success() &&;skipped 步骤的 output 读作空串)跑 7 个场景。改动后 7/7 符合预期;对改动前的文件跑同一矩阵,恰好一个场景背离 —— 即本卡片描述的缺陷。
  • node scripts/check-empty-changeset.mjs --self-test → 48 断言通过(fix(ci): Check Changeset 的 skip-changeset 判定加一次「结算读」,首跑不再结构性必红 (#6378) #6429 时为 43)。
  • pnpm check:empty-changeset / check:workflow-status-functions / check:nul-bytes / pnpm lint / check-adr-0087-registration(commit 后跑)全绿。
  • 三次消融全部转红(见下)。

推理(未执行,也不声称已验证):

  • 真实 GitHub Actions runner 上的行为。模拟器执行的是表达式,不是 GitHub。success() 隐含包裹、skipped 步骤 output 为空串这两条语义来自文档与本文件既有设计,未在真实 runner 上重跑。
  • 触发条件(标签晚到 git 基准真的失败)无法按需构造,本仓库也无已知实例 —— 这正是该缺口当初被记录而非修复的原因。
  • [ "$ADDED" -eq 0 ]ADDED 为空串时返回非零、从而使 if 走 else 分支(即「若裁决步骤放在判定步骤之后会得到伪绿」)—— 由 POSIX test 语义推得,并据此把裁决步骤放在判定步骤之前;未在 runner 上实测该伪绿。

负向断言的对称配对

「豁免的 PR 不再被红」若单独成立可以是空洞的(步骤根本没跑,或 fixture 压根到不了它)。因此矩阵里 D 与 E 成对:

  • D(修复方向):基准死 + 标签在窗口内落地 → 绿。非空洞:日志显示 Settle the skip-changeset window 确实运行了,裁决步骤是被自己的 if: 跳过的,不是因为 job 已经失败。
  • E(正向配对):基准死 + 标签始终不来 → ,且红在 Require a usable diff base。基准真的不可用且 PR 未被豁免时仍然失败。

改动前后 E 都是红(判决未变,只换了地址);只有 D 从红变绿。

CONSUMER 断言的边界决定

#6429 有意把这一步排除在判定步骤集合外,谓词是「跑 check-*.mjs 或发出 no-changeset 错误」,理由是不想把一个没论证过的豁免钉成契约。路线 3 改变了这个边界,因此断言必须同步外扩并给出论证(本 PR 不做静默放宽):

新谓词 = 「跑 check-*.mjs 含非注释的 exit 1」,计数 4 → 5。论证:

另加三条:Resolve the diff base 不得就地失败、必须写 base_error、裁决步骤必须存在且仍 exit 1。最后一条是正向写法,正是因为负向断言可被「步骤没跑」空洞满足。

同时把结算读的条件由子串断言升级为整条断言 —— 否则后来者再挂一个 || 任意条件 会静默通过并重新向所有 PR 收取等待。

反向验证(先声明,后执行)

声明(运行前写下):

  1. 整体回退 pr-automation.yml → 红,首条为结算读整条断言;chunks.length === 5 仍成立(旧文件里 diffbase 自己的 exit 1 恰好填满第五格),因此不会由计数断言抓住。
  2. 仅给 Resolve the diff base 加回 exit 1 → 红在「不得就地失败」。
  3. 仅删除裁决步骤 → 红在 chunks.length === 5(found 4)。

观察:三条方向全部相符,且比声明更强 —— 消融 2、3 各触发 3 条断言(我只点名了 1 条)。消融 1 的 6 条中第一条正是结算读整条断言,且确实没有 chunks.length 失败,与声明中那个反直觉的预测一致。

模拟器侧:对改动前文件跑同一矩阵,仅 D 背离(RED (failed at: Resolve the diff base)),E 保持红。

门禁

门禁 结果
pnpm lint(eslint,已确认未 ignore 改动脚本) pass
node scripts/check-empty-changeset.mjs --self-test pass(48 断言)
pnpm check:empty-changeset pass
pnpm check:workflow-status-functions pass(22 workflow / 41 job)
pnpm check:nul-bytes pass(6109 文件,无裸控制字节)
check-adr-0087-registration(commit 后跑) pass(67 断言)
actionlint / yamllint 本仓库没有,未替代为别的脚本;YAML 有效性由 yaml 包解析确认

Changeset

无。CI 内部改动,无对外发布面 ⇒ 建 PR 时即加 skip-changeset

本 PR 是否影响对它自己的判定

是,需要写明。 本 PR 改的正是裁决自己的那个 job,但判定用的是 main 上的 workflow 定义pull_request 事件按 base 分支的 workflow 执行),因此本 PR 的 changeset-check 走的是改动前的逻辑;改动只对合并后的 PR 生效。

更值得记下的一点:check-empty-changeset.mjs --self-test(连同 check-adr-0087-registrationcheck-changeset-no-majorpr-automation.ymlchangeset-check 里跑,没有接进 lint.yml。而该 job 在 PR 带 skip-changeset 时整体豁免 —— 本 PR 正是这种情况。所以本 PR 修改的那些断言,不会在本 PR 的 CI 上执行,上面的 48 断言与三次消融全部是本地跑的。这条已作为观察类 finding 另单记录。


Generated by Claude Code

`changeset-check` 里能让 PR 变红的步骤共五个。PR #6429#6378)让其中四个同时
认两次活标签读取(快路径读 + 结算读),第五个 `Resolve the diff base` 只带快路径
守卫,却自带两处 `exit 1`(事件无 base 分支;`git merge-base` 算不出来)。它跑在
结算读之前,因此一个在窗口内(实测 +10..45s,正是常态)才落地的 `skip-changeset`
标签对它不可见 —— 标签晚到叠加 git 基准真的失败时,它会在结算读有机会纠正之前
把一个本该豁免的 PR 判红。

本次采路线 3:该步骤只「报告」不「裁决」。两处 `exit 1` 改为写 `base_error`
output 并 `exit 0`,裁决下移到新增的 `Require a usable diff base` —— 该步骤同时
认两次读取,是全 job 中唯一见过结算后标签状态的位置。

门禁没有被放宽:基准不可用对任何未被两次读取豁免的 PR 仍然 exit 1,#4690 管的是
判决本身而不是判决的位置。计数步骤与三个工具链步骤同步加上 `base_error == ''`
守卫 —— 对计数步骤是正确性(空 `$MERGE_BASE` 下 `git diff` 失败在管道中段,末端
`tr` 仍返回 0,该步会报出伪造的 `added=0`,把判决引向完全错误的错误信息),对工具
链步骤是成本与可读性(该路径上唯一的红只会是裁决步骤本身)。结算读的触发条件加一个
析取项,是同一条不变式而非放宽:等待仍只向「将红的 PR」收取,基准不可用同样是将红。

CONSUMER 断言的边界随之外扩并写明理由:判定步骤集合由「跑 check-*.mjs 或发出
no-changeset 错误」改为「跑 check-*.mjs 或含非注释的 exit 1」,4 → 5 条,并新增
「`Resolve the diff base` 不得就地失败」「裁决步骤必须存在且仍 exit 1」。#6434 的
缺口正是因为旧谓词看不见一个它没被告知的可失败步骤才存在,新谓词把这一类连带收进来。
43 → 48 条断言;三次消融(整文件回退 / 仅回退该步骤 / 仅删除裁决步骤)全部转红。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BDmDsu2575gDxeMCxXhDE3
@vercel

vercel Bot commented Aug 8, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
objectstack Ignored Ignored Aug 8, 2026 2:43am

Request Review

@github-actions github-actions Bot added the size/m label Aug 8, 2026
@hotlong hotlong added skip-changeset PR has no user-facing published change; bypasses the changeset gate and removed size/m labels Aug 8, 2026 — with Claude
@github-actions github-actions Bot added the ci/cd label Aug 8, 2026
@hotlong
hotlong marked this pull request as ready for review August 8, 2026 02:43
@hotlong
hotlong enabled auto-merge August 8, 2026 02:43
@hotlong
hotlong marked this pull request as draft August 8, 2026 02:52
auto-merge was automatically disabled August 8, 2026 02:52

Pull request was converted to draft

@hotlong
hotlong marked this pull request as ready for review August 8, 2026 02:56
@hotlong
hotlong enabled auto-merge August 8, 2026 02:56
@hotlong
hotlong added this pull request to the merge queue Aug 8, 2026
Merged via the queue into main with commit 3d64288 Aug 8, 2026
26 checks passed
@hotlong
hotlong deleted the claude/issue-6434-resolve-diff-base-settlement branch August 8, 2026 03:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci/cd skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

2 participants