fix(ci): 无法解析的 diff 基准改为落成 output,由已认两次标签读取的步骤裁决 (#6434) - #6508
Merged
Conversation
`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
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
hotlong
marked this pull request as ready for review
August 8, 2026 02:43
hotlong
enabled auto-merge
August 8, 2026 02:43
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
marked this pull request as ready for review
August 8, 2026 02:56
hotlong
enabled auto-merge
August 8, 2026 02:56
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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 未引用该结论,而是自己重测,结论一致:按「跑
check-*.mjs或含非注释exit 1」逐步扫描origin/main(1fe436d)上的 job,五步 / 四步带结算读的划分属实:改动后同一扫描得到 5/5 全部带两次读取。
路线选择
采路线 3。 路线 1(只补注释)被否:意图已记在 PR #6429 正文,搬一句话不解决问题。路线 2 未采用,也未测量 —— 它与 #6378 的成本结论直接冲突,本 PR 不碰结算读的位置(见下)。
改了什么
Resolve the diff base的两处exit 1→ 写base_erroroutput +exit 0;发现点仍发::warning::,使基础设施抖动在豁免路径上也留痕(否则无人会提它)。Require a usable diff base (adjudicated after the label window):同时认两次读取 +base_error != ''→exit 1。base_error == ''守卫 —— 这是正确性而非成本:空$MERGE_BASE下git diff失败在管道中段,末端tr仍返回 0,该步会报出伪造的added=0,把判决引向「你忘了写 changeset」这个完全错误的错误信息。|| base_error != ''。为什么第 5 条不是路线 2:结算读的位置没动(仍在计数之后)。#6429 的成本论证是「等待只向将红的 PR 收取」,而基准不可用同样是将红 —— 且计数步骤在该路径上被跳过,
added为'',第一个析取项无法为它发声。有可解析基准且写了 changeset 的 PR 仍完全跳过该步,零等待零 API 调用。没有任何 PR 因此新增等待。改动后的失败签名
供未来读者比对真实事故:
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 baseskipped,四个判定步骤全部 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 文件,分清楚
执行(有真实输出):
if:表达式(>-折叠正确,括号析取正确)。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 后跑)全绿。推理(未执行,也不声称已验证):
success()隐含包裹、skipped 步骤 output 为空串这两条语义来自文档与本文件既有设计,未在真实 runner 上重跑。[ "$ADDED" -eq 0 ]在ADDED为空串时返回非零、从而使if走 else 分支(即「若裁决步骤放在判定步骤之后会得到伪绿」)—— 由 POSIXtest语义推得,并据此把裁决步骤放在判定步骤之前;未在 runner 上实测该伪绿。负向断言的对称配对
「豁免的 PR 不再被红」若单独成立可以是空洞的(步骤根本没跑,或 fixture 压根到不了它)。因此矩阵里 D 与 E 成对:
Settle the skip-changeset window确实运行了,裁决步骤是被自己的if:跳过的,不是因为 job 已经失败。Require a usable diff base。基准真的不可用且 PR 未被豁免时仍然失败。改动前后 E 都是红(判决未变,只换了地址);只有 D 从红变绿。
CONSUMER 断言的边界决定
#6429 有意把这一步排除在判定步骤集合外,谓词是「跑
check-*.mjs或发出 no-changeset 错误」,理由是不想把一个没论证过的豁免钉成契约。路线 3 改变了这个边界,因此断言必须同步外扩并给出论证(本 PR 不做静默放宽):新谓词 = 「跑
check-*.mjs或含非注释的exit 1」,计数 4 → 5。论证:exit 1而非某个特定错误串,是为了让规则活得比它当初描述的那几步更久。观察单:Resolve the diff base是 Check Changeset 里唯一只认快路径标签读取的可失败步骤,标签晚到 + git 基准不可用时会红一个本该豁免的 PR #6434 这个缺口之所以存在,恰恰是因为旧谓词看不见一个它没被告知的可失败步骤 —— 下一个加进来的也会同样隐形。exit 1也不跑check-*.mjs的步骤仍在集合外。这是比它替换掉的那个更小的洞,不是没有洞。另加三条:
Resolve the diff base不得就地失败、必须写base_error、裁决步骤必须存在且仍exit 1。最后一条是正向写法,正是因为负向断言可被「步骤没跑」空洞满足。同时把结算读的条件由子串断言升级为整条断言 —— 否则后来者再挂一个
|| 任意条件会静默通过并重新向所有 PR 收取等待。反向验证(先声明,后执行)
声明(运行前写下):
pr-automation.yml→ 红,首条为结算读整条断言;chunks.length === 5仍成立(旧文件里 diffbase 自己的exit 1恰好填满第五格),因此不会由计数断言抓住。Resolve the diff base加回exit 1→ 红在「不得就地失败」。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 改动脚本)node scripts/check-empty-changeset.mjs --self-testpnpm check:empty-changesetpnpm check:workflow-status-functionspnpm check:nul-bytescheck-adr-0087-registration(commit 后跑)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-registration、check-changeset-no-major)只在pr-automation.yml的changeset-check里跑,没有接进lint.yml。而该 job 在 PR 带skip-changeset时整体豁免 —— 本 PR 正是这种情况。所以本 PR 修改的那些断言,不会在本 PR 的 CI 上执行,上面的 48 断言与三次消融全部是本地跑的。这条已作为观察类 finding 另单记录。Generated by Claude Code