fix(ci): allow-major 改为求值时刻实时读标签,红 run 不再永久红 (#5620) - #6876
Merged
Conversation
`pr-automation.yml` 的 `Guard against accidental major bumps (launch window)` 第三个 clause 一直读 `contains(github.event.pull_request.labels.*.name, 'allow-major')` —— 与 #5580 修掉的是同一个读法、同一个后果:载荷是事件触发那一刻的 快照,开 PR 后再补 `allow-major` 该 run 看不见;而 `rerun_failed_jobs` 复用同一份 载荷(pm-dispatch Operational notes 5),于是这个红 run 按构造重跑不成绿,只能靠一次 新的真实事件顶掉。 复用 #5580 的落地形状(`a9c7e6ba0` 在同一 job 新增 `id: labels` 实时读标签步骤, 以 `steps.labels.outputs.skip` 供下游 `if:` 消费):新增 `id: allow_major` 步骤, 同一个 `gh api repos/$REPO/pulls/$PR` 端点、同一个 `grep -qxF` 整行精确匹配、同一个 here-string(避免 `grep -q` 进管道后写入端吃 SIGPIPE、在 pipefail 下翻转判定)、 同一个 #4690 容忍方向 —— 读不到标签判 `allow=false`,即照常执行守卫。守卫步骤的第三个 clause 改读 `steps.allow_major.outputs.allow != 'true'`。 与 #5580 的两处刻意分歧,均因本标签在 job **末尾**被消费而非开头: 1. **不保留载荷 fast-path。** #5580 保留它是因为载荷里已有 `skip-changeset` 可跳过 整个 job(checkout + install 全免)换一次 API 调用;这里下游只剩一条 `node scripts/check-changeset-no-major.mjs`,fast-path 省不下任何东西,却会一并 保留 #5580 承认的那个反向 stale 格(事件后被**移除**的标签仍豁免本 run)。去掉后, 豁免只能由「真的观测到的标签」建立。 2. **不加 #6378 式结算窗口**,并按残留记录而非当作已解决。#6378 的等待只能记在 「注定要红」的 PR 头上,而这里的「注定要红」= 「声明了 major 且没有标签」—— 只有 `check-changeset-no-major.mjs` 知道,且它以 exit 1 表达。要据此设条件就得把 脚本跑两遍、或吞掉输出再回放,即改变该守卫的报错形状;而本步骤的**位置**已经把 竞态窗口收窄:它排在 checkout + setup-node + `pnpm install --frozen-lockfile` + 计数 + 两个 `check-*.mjs` 之后,而 job 开头的 fast-path 读位于开 PR 后约 +10s (#6378 实测 run 31204438874),标签落地延迟实测 +10..45s(#6310、#6358)。 更关键:与 #5580 的方向不同,这里的首红**有救** —— 读是实时的,补标签后 `rerun_failed_jobs` 即收敛为绿。消掉「永久红」正是 #5620 记的那笔。 **休眠不作为依据。** `check-changeset-no-major.mjs` 在 pre-release 窗口整体让位 (其 RC EXEMPTION 头注),`.changeset/pre.json` 现为 `"mode": "pre"`,故该守卫今天 不可能红、标签也用不上。它在 `changeset pre exit` 当刻自动重新武装 —— 恰是全栈 major 讨论最密集、`allow-major` 最可能被现场手工补标签的窗口。文件内注释写明了这一点。 回归钉:`scripts/check-empty-changeset.mjs` 的 consumer 自测块(该仓唯一断言本 job 标签读取形状的地方)增 5 条断言 —— 工作流内不得再出现 `allow-major` 的载荷读法、 恰好一处实时 `grep -qxF 'allow-major'`、守卫必须消费 `steps.allow_major.outputs.allow`、 实时读须同时尊重两处 skip-changeset 读、`allow=true` 只能写一次且必须在真正观测到 标签的 grep 之后(容忍方向按位置钉死,而非数分支)。 ⛔ 未动:job 名与既有 step 命名约定(无新增 job,必需检查上下文不受影响)、计数逻辑、 `BASE_SHA`/merge-base、release 与 pre-mode 策略、`docs/adr/**`。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01F8q5J1MQyocgtNspb15fSn
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
os-project-manager
marked this pull request as ready for review
August 9, 2026 02:29
This was referenced Aug 9, 2026
os-project-manager
pushed a commit
that referenced
this pull request
Aug 9, 2026
…#6509 Weakens the live allow-major matcher in pr-automation.yml from `grep -qxF` to `grep -qF`, i.e. reintroduces exactly the #5620-class defect that PR #6876's consumer assertions were added to catch — and which never executed once on #6876's own CI, because that PR carried skip-changeset. This PR carries skip-changeset too. Predicted direction: `Check Changeset` skips its self-test steps again, and the NEW lint.yml step goes RED. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01F8q5J1MQyocgtNspb15fSn
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 #5620
.github/workflows/pr-automation.yml的Guard against accidental major bumps (launch window)步骤,第三个 clause 一直是载荷快照读法:与 #5580 修掉的是同一个读法、同一个后果:载荷是事件触发那一刻的快照,开 PR 后再补
allow-major,该 run 看不见;而rerun_failed_jobs复用同一份载荷(pm-dispatch Operational notes 5),于是这个红 run 按构造重跑不成绿,只能等一次新的真实事件顶掉。参照:#5580 落地的形状,直接复用
a9c7e6ba0(PR #5625,fix(ci): Check Changeset 实时读 skip-changeset 标签)在同一个 job 新增了一个实时读标签步骤:.github/workflows/pr-automation.yml:194(当前main)——- name: Re-read this PR's labels live (the event payload can predate them)/id: labels.github/workflows/pr-automation.yml:208——gh api "repos/$GITHUB_REPOSITORY/pulls/$PR_NUMBER" --jq '.labels[].name'.github/workflows/pr-automation.yml:222——grep -qxF 'skip-changeset'整行精确匹配,here-string 喂入steps.labels.outputs.skip,由下游每个步骤的if:消费本 PR 照抄这个形状:新增
id: allow_major步骤,同一个端点、同一个整行精确匹配、同一个 here-string、同一个 #4690 容忍方向(读不到标签判allow=false,即照常执行守卫 —— 读不到输入的门什么也没验证,据此发一张全栈 major 的豁免正是那个反模式),守卫步骤第三个 clause 改读steps.allow_major.outputs.allow != 'true'。两处刻意分歧(均因本标签在 job 末尾被消费,而非开头)
1. 不保留载荷 fast-path。 #5580 保留它,是因为载荷里已经有
skip-changeset时可以跳过整个 job(checkout、install 全免)换一次 API 调用。这里下游只剩一条node scripts/check-changeset-no-major.mjs,fast-path 省不下任何东西,却会一并保留 #5580 自己承认的那个反向 stale 格(事件后被移除的标签仍豁免本 run)。去掉它之后,豁免只能由「真的观测到的标签」建立。2. 不加 #6378 式结算窗口 —— 按残留记录,不当作已解决。#6378 的等待只能记在「注定要红」的 PR 头上;而这里的「注定要红」= 「声明了 major 且没有标签」,只有
check-changeset-no-major.mjs知道,且它以 exit 1 表达。要据此设条件,就得把脚本跑两遍、或吞掉输出再回放 —— 即改变该守卫的报错形状。而本步骤的位置已经把窗口收窄:它排在 checkout + setup-node +pnpm install --frozen-lockfile+ 计数 + 两个check-*.mjs之后,而 job 开头那次 fast-path 读位于开 PR 后约 +10s(#6378 实测 run 31204438874),标签落地延迟实测 +10..45s(#6310、#6358)。更关键的是:与 #5580 的方向不同,这里的首红有救 —— 因为读是实时的,补标签后rerun_failed_jobs即收敛为绿。消掉「永久红」正是 #5620 记的那笔。休眠不作为依据
check-changeset-no-major.mjs在 changesets pre-release 窗口整体让位(其 RC EXEMPTION 头注),.changeset/pre.json现为"mode": "pre"/"tag": "rc",所以该守卫今天不可能红,allow-major也用不上。让它重新武装的是
changeset pre exit—— 那一刻守卫自动恢复,而那恰是全栈 major 讨论最密集、allow-major最可能被人在gh pr create之后几秒手工补上的窗口。休眠是一个会结束的条件,不是把修法做浅的理由;文件内注释把这句话写在了步骤上方。验证
工作流无法本地运行,所以验证分三层,每层都先声明预期方向再跑:
A. 门禁(真实输出)
pnpm check:empty-changesetpnpm check:workflow-status-functionsif:)pnpm check:node-versionpnpm check:nul-bytespnpm exec eslint scripts/check-empty-changeset.mjs --no-inline-configcheck:workflow-status-functions做的是真 YAML parse(非 grep),它绿即证明改后的工作流仍能被解析;check:node-version绿即证明没有碰到 setup-node 矩阵。未新增/未重命名任何 job(只新增一个 step),所以必需检查的 context 名不受影响 —— 这是 #5617 点名的那个风险面。B. 反向验证(预期方向:红) —— 只把工作流回退到载荷读法、保留新钉子,
check-empty-changeset --self-test应转红并点名 #5620。实跑 exit=1,5 条失败,首条即:还原后 exit=0。
C. shell 层:用 stub
gh驱动 YAML 里抽出来的真实脚本(与 PR #5625 同法)—— 用yaml解析工作流、取出id: allow_major步骤的run原文(914 bytes,含 YAML 块标量去缩进),因此跑的是将要上 CI 的字节,不是手抄件。9 场景 x 2 方言(bash -e,以及bash -eo pipefail)= 18/18 通过:allow=trueallow=trueallow-majorallow=falseallow-major-audit/not-allow-majorallow=falsegh api失败(403)allow=false+ warningallow=false+ warningallow-major在最后(pipefail 下的 SIGPIPE 压力)allow=true该 harness 自身能不能红,也做了两次 ablation 证明(预期方向:红):把
grep -qxF削成grep -qF→ 两条近似名场景 x 2 方言共 4 例转红;把「标签读不到」分支的allow=false改成allow=true→ 403 场景 x 2 方言共 2 例转红。还原后 18/18。决策真值表(改前 -> 改后),4 格里变 2 格,两格都朝实时真相走 —— 注意它不是单向放宽:
rerun_failed_jobs那一格单列:改前载荷被复用 -> 永久红;改后实时读重跑 -> 收敛为绿。仍未被证明的部分,明说:GitHub Actions 的表达式求值、
steps.allow_major.outputs.allow的实际串接、以及gh在 runner 上的真实行为,只有在 runner 上才能验;更麻烦的是本条处于休眠,check-changeset-no-major.mjs在 pre-mode 下不会失败,所以任何 CI run 都不会走到「守卫真的执行并判定」那一步,直到changeset pre exit。B 层的静态钉子正是为这个空窗准备的 —— 它不依赖守卫被执行。回归钉
scripts/check-empty-changeset.mjs的 consumer 自测块(该仓库唯一断言本 job 标签读取形状的地方,#6129/#6378/#6434 都钉在这里)新增 5 条断言:工作流内不得再出现allow-major的载荷读法;恰好一处实时grep -qxF 'allow-major';守卫必须消费steps.allow_major.outputs.allow;实时读须同时尊重两处 skip-changeset 读(被豁免的 PR 不该为一个不会跑的守卫买一次 API 调用);allow=true只能写一次且必须在真正观测到标签的 grep 之后(容忍方向按位置钉,不按分支计数)。放在这里而不是另起门禁,理由与休眠同源:这一格现在没有任何 CI run 能执行到,所以静态钉子是它唯一的守护,而把它放在已经钉着同一个 job、同一批标签读的那个自测块里,下一个读者只需要记住一个地方。
边界
⛔ 未动:job 名与既有 step 命名约定(无新增 job)、计数逻辑、
BASE_SHA/ merge-base、docs/adr/**、release 与 pre-mode 策略、check-changeset-no-major.mjs本身。本 PR 只改
.github/与仓库根scripts/(均不随任何 npm 包发布),不发布任何东西,故取skip-changeset标签而非 changeset(工作流自身处方的 route 2)。Generated by Claude Code