fix(ci): Check Changeset 以 merge-base 为差异起点,main 漂移不再算作本 PR 新增 - #6192
Merged
Conversation
`pr-automation.yml` 的 Check Changeset 用 `github.event.pull_request.base.sha` 作差异起点。这个 sha 在 PR **创建时**被冻结,而 `actions/checkout@v7` 在 `pull_request` 事件上签出的是 merge ref(`refs/pull/N/merge`)—— 一个 parent^1 为**当前** main tip 的合并提交。两者之间的 main 漂移,整段被 `--diff-filter=A` 记成「本 PR 新增的文件」:别人 PR 合进 main 的 changeset,在本 PR 眼里就是本 PR 加的。PR #6117 同一份 diff、零 changeset,02:22Z 判红、02:39Z 判绿,只因为这 17 分钟里 main 多了两个带 changeset 的 PR。这是发版安全门禁上的**假绿**,后续没有 任何一步会把它纠回来。 改法:差异起点改为 `git merge-base origin/$BASE_REF HEAD`。在 merge ref 上它恰好 落在 parent^1,于是 diff 只剩本 PR 自己那侧 —— 早跑晚跑同判。三个 BASE_SHA 消费 者一起改(计数步骤、`check-empty-changeset.mjs --base`、以及脚本自己的 base 语义), checkout 保持不变。 `scripts/check-empty-changeset.mjs`:`scan()` 内部把 base 解析为 `merge-base(base, head)`。CI 路径上这一步是幂等的,它修的是另一半 —— 默认 `--base origin/main` 的本地路径:`changeset pre exit` 从 main 删掉已消费的 changeset 之后,两点 diff 会把未 rebase 分支上仍带着的存量空 changeset 全部读成 「本分支新增」(实测 2 个 fixture → 2 条假红,merge-base → 0 条)。 self-test 21 → 36 条断言,新增两个方向 + 消费者断言:main 漂移**不得**改变判定 (merge ref fixture,真实两父提交)、本 PR 自己新增的空 changeset **仍须**判红、 以及直接读 `pr-automation.yml` 断言计数与 `--base` 都吃 `$MERGE_BASE` —— 少了最 后这组,workflow 明天被改回冻结 sha 也不会有任何一条断言变红,那正是 #6129 要堵 的「换个形状回来」。 `check-changeset-no-major.mjs` 实测未受影响:它不读 `--base`、不做 git diff, `readdirSync('.changeset')` 扫整个目录,不存在 base 选取问题。#5620 的 allow-major 步骤逐字未动。 Refs #6129 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
|
This was referenced Aug 7, 2026
hotlong
marked this pull request as ready for review
August 7, 2026 10:40
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 #6129
Check Changeset 的差异起点从冻结的
github.event.pull_request.base.sha改为与 base 分支的 merge-base,让判定只取决于本 PR 自己那侧 —— 同一份 diff,早跑晚跑同判。机制(在真实两父提交上验过,不是推的)
actions/checkout@v7在pull_request事件上默认签出 merge ref(refs/pull/N/merge),HEAD 是一个合并提交,parent^1 = 建 ref 那一刻的 main tip,parent^2 = PR head。而base.sha在 PR 创建时冻结,不随 main 前进。两者之间的 main 漂移,整段被--diff-filter=A记成「本 PR 新增」。在临时仓库里复刻 #6117 的形状(PR 只改 workspace 文件、零 changeset;其间 main 合入两个别人的带 changeset PR):
对照组(PR 自己真加了 changeset):OLD=3(2 个冒名 + 1 个自己的),NEW=1,且点名的正是自己那个文件 —— 没有矫枉过正。
两个看着像修法、实际不是的写法,都写进了 workflow 注释:
git diff base.sha...HEAD(三点)。三点 =merge-base(base.sha, HEAD)..HEAD,而base.sha本来就是 HEAD 的祖先,自己就是自己的 merge base,计数一动不动(实测仍是 2)。HEAD^1。在 merge ref 上对,但哪天有人给 checkout 加了ref:,parent^1 就变成 PR 的上一个提交 —— 静默地全错。merge-base在两种 checkout 下都对,所以选它。checkout 保持不变
不需要改。merge-base 在 merge ref 上恰好落在 parent^1,diff 出来就是本 PR 自己那侧。改签 head ref 会换掉本 job 每一步看到的整棵树(
pnpm install --frozen-lockfile与脚本读到的 head 都变),为零收益付大得多的爆炸半径。三个消费者
ADDED):吃$MERGE_BASE。check-empty-changeset.mjs --base:同一个$MERGE_BASE。同款偏差、方向相反 —— 冻结 base.sha 会把 main 新增的空 changeset 报到一个碰都没碰过该文件的作者头上(假红)。scan()内部把 base 解析为merge-base(base, head)。CI 路径上这一步幂等(传进去的已是 merge base);它修的是另一半 —— 默认--base origin/main的本地路径。changeset pre exit从 main 删掉已消费的 changeset 之后,两点 diff 会把未 rebase 分支上仍带着的存量空 changeset 全读成「本分支新增」。实测:2 个 fixture 在 main 上被删 → 两点 diff 报 2 条假红,merge-base 报 0 条。workflow 注释里那句「deferred to the nextchangeset pre exit」就是这个触发条件。新增一个
Resolve the diff base步骤统一算一次并写进$GITHUB_OUTPUT,两个消费者读同一个值 —— 一个地方可审计。算不出来时响亮失败(#4690),绝不回退到冻结 sha:那正是本单要杀的缺陷。步骤还会把漂移量打进日志(main has moved N commit(s) since it was frozen)—— #6129 藏这么久,一部分原因就是没有任何一行日志说过 diff 从哪个提交开始。check-changeset-no-major.mjs:实测未受影响它既不读
--base也不做任何 git diff,readdirSync('.changeset')扫的是签出树上的整个目录,压根不存在 base 选取问题。方向上也不同:多出来的文件只会让它更容易判红(保守),不会产生假绿;何况 RC 窗口内它整个让路。#5620 的 allow-major 步骤逐字未动:skip-changeset豁免路径同样一行未改:新步骤带着和其它步骤完全相同的if: steps.labels.outputs.skip != 'true',#5580 的实时读标签仍是权威。self-test:21 → 36 条断言
⛔ 本单明确要求「不要只改 workflow 而不动 self-test」。新增的是两个方向 加上消费者断言:
changeset pre exit形状,钉住scan()内部那半。pr-automation.yml,断言计数与--base都吃$MERGE_BASE、且没有任何git diff拿冻结 sha 当端点。只驱动scan()的 self-test 看不见假绿所在的那半(它在 YAML 里);少了这组,明天把 workflow 改回冻结 sha,36 条断言一条都不会红 —— 那就是「换个形状回来」。反向验证(方向先声明,再跑)
git checkout origin/main -- pr-automation.yml)scan()那些不动scan()直接用 raw base)第二行值得读一遍,它不是模板预期:在 merge ref 上,base 分支 tip 本来就是 parent^1,所以哪怕不做 merge-base,从分支 tip 两点 diff 也已经是无漂移的。也就是说 —— CI 那半的漂移是被消费者断言钉住的,不是被
scan()内部的 merge-base 钉住的。两半各有各的钉子,报告如实写成两行而不是合并成一句。门禁
node scripts/check-empty-changeset.mjs --self-test36 条绿;pnpm check:empty-changeset真跑绿;pnpm check:workflow-status-functions绿(新步骤是 step 级if:读steps.*.outputs.*,明确在该门禁 scope 之外);pnpm check:nul-bytes绿 + 对改动文件另跑了一遍控制字符自扫(零命中);eslint 绿;YAML 解析通过、步骤顺序正确。本 PR 只动 workflow 与 scripts,不发布任何包,按路线 2 打
skip-changeset标签。Generated by Claude Code