Skip to content

fix(ci): Check Changeset 的失败文案不再把「空 changeset」当作与标签等价的出路 (#5292) - #5467

Merged
os-zhuang merged 1 commit into
mainfrom
claude/issue-5292-changeset-prescription
Aug 5, 2026
Merged

fix(ci): Check Changeset 的失败文案不再把「空 changeset」当作与标签等价的出路 (#5292)#5467
os-zhuang merged 1 commit into
mainfrom
claude/issue-5292-changeset-prescription

Conversation

@os-zhuang

@os-zhuang os-zhuang commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Fixes #5292

问题(前提已对 origin/main 复核)

origin/main @ 61fde5e44:

  • .github/workflows/pr-automation.yml:108 —— 失败文案仍写着 an empty changeset is fine for changes that release nothing;
  • 同文件 :102-104 —— 计数逻辑上方的注释把空 changeset 称作 on par with the skip-changeset label

两处都在,issue 的前提成立。

这两条出路在本闸门的计数里确实等价,但changesets/action 眼里不等价:

  • skip-changeset 标签 = 闸门层面的豁免,不产生任何输入;
  • 空 frontmatter 的 changeset = 喂给 action 的真实输入。待消费的 changeset 全为空时,action 走 hasChangesets && !hasNonEmptyChangesets 分支,打印 All changesets are empty; not creating PR,0 秒返回 —— 不建版本 PR、不发布,而 Release run 全绿。

这条机制不是本 PR 的推断,仓内已有记录:.github/workflows/release.yml:203-208 的恢复步骤注释原文写着 "changesets/action reaches its publish branch only with ZERO pending changesets, and an EMPTY changeset still counts ... and a GREEN run (#4898)"。

所以旧文案是一条主动的错误处方:开发者(或 agent)刚撞上红闸门的那一刻,它把一个已知会静默卡死发布的输入推荐给对方。

改动

只动文案与注释,不动计数逻辑(按分诊口径)。

失败时改为输出三条有序出路:

  1. 确实要发版 → pnpm changeset 并点名 package;
  2. 不发版(.github/.claude/docs/content/examples/、纯测试等)→ 首选 skip-changeset 标签,并说明它为什么安全(不产生 action 输入);
  3. 空 frontmatter changeset —— 依然合法、依然被计数,但降级为带警告的次选:点名它是 action 的真实输入、点名 #4898、点名「全空则 0 秒静默不发版且 Release 全绿」。

上方注释同步改写,去掉 on par with the skip-changeset label 这个错误等价,同时明确保留「仍然计数」这一事实。

完整对照写进 job log(作者用 gh run view --log-failed 或在 UI 里展开这一步时看到的就是它),::error:: 注解保留为 Checks 页签上的单行版本 —— 一行装不下两条出路的取舍。

验证:计数逻辑逐字未动

origin/main 与本分支的 run: 脚本从 YAML 里各自抽出,喂同一组 fixture 仓库跑,比对退出码:

fixture old exit new exit
A. PR 不加任何 changeset 1 1
B1. PR 加一个非空 changeset 0 0
B2. PR 加一个空 frontmatter changeset 0 0
C. PR 只加 .changeset/README.md 1 1

四个 fixture 退出码完全一致。B2 是关键的一格:空 changeset 仍然计数、仍然放行 —— 与今日先例(#5453 / #5454,两者都是带空 frontmatter changeset 过的这道门)保持一致。唯一的差异是 A / C 打印的文案。

关于反向验证方向的老实话:这里不存在「回退改动 → 测试转红」那种方向。本次修改按分诊口径刻意不碰判定,四个 fixture 的判定本就应当前后一致;能证明改对了的,是「退出码逐格相同」+「文案不再包含 an empty changeset is fine」这两条,而不是一次红绿翻转。仓内全域 grep 确认 empty changeset is fine 改后 0 处命中,且 skip-changeset 只在本文件出现,没有第二份需要同步的副本。

YAML 与 heredoc 也实测过:yaml.safe_load 解析通过,抽出的脚本里终止符 MSG 落在第 0 列(YAML 会剥掉块标量的公共缩进,所以终止符必须停在本块基准缩进 —— 这点已写进代码注释,防止后来者重新缩进时踩坑)。

门禁:node scripts/check-nul-bytes.mjsOK (scanned 5447 tracked text file(s) ... no raw NUL bytes);另按 AGENTS.md 的控制字符纪律对改动文件做了越界自扫 grep -naP '[\x00-\x08\x0b\x0c\x0e-\x1f]' → 无命中。

清点:.changeset/ 的空 frontmatter 残留(分诊范围 ②)

origin/main @ 61fde5e44 快照:

数量
.changeset/*.md(不含 README) 1065
非空 frontmatter 893
空 frontmatter 172

.changeset/pre.json 处于 mode: pre / tag: rc,记录了 860 个已被 changeset version 消费过的 changeset id。以此切分那 172 个空文件:

  • 140 个已记录在 pre.json —— 即已被某次 RC 版本运行消费过、按 pre 模式保留在盘上的残留。这正是 :95-101 注释描述的、也是本闸门改用 BASE_SHA diff 计数所要绕开的那堆残留。
  • 32 个未记录 —— 属于下一次 release run 的待消费输入。逐个归因:
    • 26 个在可见历史根提交 b49ccfdf 就已存在。该提交无父提交(仓库历史起点),git 无法再往前归因。
    • 6 个可归因,且其引入 PR 全部只碰非发布路径:
空 changeset 引入提交 该提交碰的路径
adr-0076-status-line-calibration.md 0c1660306 docs/
ci-filter-implicit-success-guard.md b8add3758 .github/
pm-dispatch-three-axis-decision-frame.md 3681d2a7d (#5453) .claude/
showcase-register-contact-views.md ed0d2aac0 (#5438) examples/
tenancy-modes-doc-posture-rewrite.md 23e9c9004 content/
widget-contract-theme-token-vocabulary.md 31b05372e (#5454) content/

结论:未发现无主残留。 每个可归因的待消费空 changeset 都来自一个只改非发布路径的 PR,即分诊评论所说的「正当存在的『releases nothing』声明」,按口径一个都没有删

当前 #4898 的 0 秒分支并未被触发:待消费的 changeset 里还有 173 个非空,所以 hasNonEmptyChangesets 为真。换句话说今天的残留是账面事实,不是正在燃烧的火。

一条顺手核实、写进了新文案的事实

新文案第 3 条里那句「空 changeset 也给不了你标签给不了的东西」不是修辞,是实测:

  • 取三个已被消费过的空 changeset(adr-0044-revise-service-owned-noteci-node-22-pinduplicate-fix-guard),把它们的正文原句拿去 grep 全仓 CHANGELOG.md各 0 处命中;
  • 对照组:任取一个非空 changeset 的正文首句 → 命中 packages/spec/CHANGELOG.mdpackages/cli/CHANGELOG.md 2 处

即:空 frontmatter 不点名任何 package,正文因此进不了任何 CHANGELOG。它相对标签没有任何正向收益,却多出「是 action 真实输入」这一项风险 —— 这正是把它降为次选、而不是并列的依据。

(顺带说明:这条事实同样加强了 issue 里那个「干脆禁止空 changeset 进 .changeset/」的备选方案。该方案按分诊口径明确不在本单,牵动发布机器语义,留给维护者裁定,本 PR 不做。)

本 PR 自身怎么过这道门

按 walking the talk,走新文案的首选出路:skip-changeset 标签(先例 PR #5290)。本 PR 只改 CI workflow 文案,不发布任何 package —— 正是新文案第 2 条描述的情形。故不附 changeset

skip-changeset 标签已随本 PR 一并加上(见 Labels),Check Changeset 因此对本 PR 豁免 —— 这正是新文案第 2 条推荐的走法。

…talled the release (#5292)

The gate's failure message offered "an empty changeset" and the
`skip-changeset` label as two equal ways out, and the comment above the
counter called them "on par". They are equal to THIS gate's counting, but
not downstream: the label is a gate-level exemption that produces no input
for changesets/action, while an empty-frontmatter changeset is a real input
to it. When every pending changeset is empty the action takes its
`hasChangesets && !hasNonEmptyChangesets` branch, prints "All changesets
are empty; not creating PR" and returns in 0 seconds -- no version PR, no
publish, Release run green. That is #4898, which stalled 17.0.0-rc.2.

The message was therefore an active mis-prescription: it handed a known
release-stalling input to whoever had just hit the wall.

Rewritten to three ranked routes, with the label preferred and the empty
changeset demoted to a warned last resort that names #4898. The counting
logic is untouched: an empty changeset still counts, so today's precedent
(#5453 / #5454, both merged behind empty changesets) stays valid.

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

vercel Bot commented Aug 5, 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 5, 2026 1:11pm

Request Review

@os-zhuang os-zhuang added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Aug 5, 2026 — with Claude

Copy link
Copy Markdown
Contributor Author

附:新文案在本 PR 上的实跑证据(CI 自证)

本 PR 恰好把两条出路都跑了一遍,而且顺序刚好是「先撞门、再按新文案的首选出路脱困」——

1. 开 PR 时(标签尚未加上)→ Check Changeset 红,打印的正是新文案
run 31009072256 / job 92316255763,log 末段原文:

  3. An empty-frontmatter changeset also satisfies this gate and stays
     legal -- but it is a LAST RESORT, not the quick way past a red check.
     Unlike the label it is a REAL INPUT to changesets/action: when every
     pending changeset is empty, the action takes its
     "hasChangesets && !hasNonEmptyChangesets" branch, prints
     "All changesets are empty; not creating PR", and returns in 0 seconds
     -- no version PR, no publish, and the Release run still goes GREEN.
     That is #4898, which silently stalled 17.0.0-rc.2. It also buys you
     nothing the label does not: an empty changeset names no package, so
     its body reaches no CHANGELOG.

If you are unsure, take route 2. A wrong 'skip-changeset' label is caught by
review; a wrong empty changeset is caught by nobody.
##[error]This PR adds no changeset. If it releases nothing, apply the 'skip-changeset' label (preferred); otherwise run 'pnpm changeset' and name the packages. An empty-frontmatter changeset also passes this gate, but it is NOT equivalent to the label -- it is a real input to changesets/action, and an all-empty set stalls the release silently and greenly (#4898). Full comparison in this step's log.
##[error]Process completed with exit code 1.

这一跑同时证掉了两件本地无法证的事:heredoc 在 GitHub Actions 真实 runner 上的缩进逐字保真(终止符落位正确),::error:: 注解正常渲染成 ##[error]

2. 加上 skip-changeset 标签后 → Check Changeset skipped
run 31009087759 / job 92316311539 —— job 级 if: 豁免生效,闸门整个不跑。

也就是说:红 → 读文案 → 走第 2 条(标签)→ 绿。这条路径就是新文案推荐的那条,本 PR 自己走了一遍。旧文案在同样这一刻会把第 3 条(空 changeset)与第 2 条并列推荐给作者,而在一个改发布闸门的 PR 里种空 changeset,正是 #5292 指出的最难查的情形。


Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Check Changeset 的失败文案把「空 changeset」推荐为出路 —— 而那正是 #4898 静默卡死发布的输入

2 participants