Skip to content

ci(workflows): objectui-pin-freshness 补 merge_group 触发器,队列世代走 advisory 车道 (#6121) - #6252

Merged
hotlong merged 1 commit into
mainfrom
claude/issue-6121-pin-freshness-merge-group
Aug 7, 2026
Merged

ci(workflows): objectui-pin-freshness 补 merge_group 触发器,队列世代走 advisory 车道 (#6121)#6252
hotlong merged 1 commit into
mainfrom
claude/issue-6121-pin-freshness-merge-group

Conversation

@hotlong

@hotlong hotlong commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Fixes #6121

问题

.github/workflows/objectui-pin-freshness.yml 的注释明文邀请维护者把 Console Pin Freshness 加进 main 的分支保护 required 集,而该 workflow 只订阅 pull_request + workflow_dispatch,没有 merge_group:。合并队列会在 gh-readonly-queue/** 世代上重新求值 required checks;一个在队列世代上根本不产生 check run 的 required context,会让每个队列世代永久停在 "Expected — waiting for status"(#3622 是这个死锁的原型)。

同一文件自述『a required context that reports nothing leaves every PR stuck』——它写对了 PR 那一半,漏掉了队列那一半,然后据此发出了邀请。

风险窗口是活的,不是纸面推演。event=merge_group 查本仓 Actions:共 2745 次运行,最近几批是今天(2026-08-07)12:00 前后的 gh-readonly-queue/main/pr-6240-… / pr-6210-… / pr-6233-…(CI、Lint & Type Check、Spec Liveness Check 三个 workflow)。队列每天都在跑,只差有人照注释做。

前提复核(origin/main@01faeb1):22 个 workflow 中订阅 merge_group 触发器的是 ci.ymllint.ymlspec-liveness-check.yml 三个,本文件不在其列 —— issue 的前提成立。

改法(单文件三处)

  1. on: 增加裸键 merge_group: —— 与 ci.yml / lint.yml / spec-liveness-check.yml 的同族写法一致(裸键,附注释指向 ci.yml 的完整说明),不是 types: [checks_requested]
  2. 邀请注释改写为「带前置条件的邀请」 —— 见下方前后对照。
  3. 车道判据改为按事件显式分类 —— 这一处不是范围外的顺手改,而是「加触发器」这件事本身的必要组成,理由在下一节测量给出。

⛔ 按分诊钉死:没有Console Pin Freshness 加进任何 required 集(设置面归维护者,#5617),也没有碰任何其他文件。

注释前后对照(第 34-39 行那段邀请)

改前(只说「维护者必须去加」,不说前提):

REQUIRED-CHECK ENFORCEMENT IS NOT SELF-DECLARED
A workflow cannot make itself required. A maintainer must add the Console Pin Freshness context to the branch-protection rule for main … Until then this workflow REPORTS on the release PR without blocking the merge button.

改后:保留上面全部原文(那段观察本身是对的),追加前提与验证义务 ——

⚠️ THE PRECONDITION IS THE merge_group: TRIGGER BELOW (#6121). Adding this context to a required set is safe ONLY BECAUSE that trigger now exists. 上面那段讲的是规则的 PR 那一半;branch protection 会在 gh-readonly-queue/** 世代上再求值一次,所以在队列世代上不产生 run 的 required context 会把每个世代永久停住(#3622)。
FUTURE EDITORS: 在把本 context 加入、或保留在任何 required 集之前,先确认 merge_group: 仍在 on: 里。删掉那个触发器不会响,它只会让全仓每个 PR 的合并队列死锁。

即:把它原本正确的那半句观察,接上了它漏掉的队列那半句。

为什么第 3 处必须一起改(测量,不是猜测)

步骤里的车道判据原文是:

if [ "$EVENT" != "pull_request" ] || [ "$HEAD_REF" = "changeset-release/main" ] || [ "$PR_TITLE" = "chore: version packages" ]; then

on: 只有两个条目的年代,!= pull_request 就等于「workflow_dispatch」。merge_group: 恰好是这个代理失效的时刻:merge_group 事件下该否定式为真 ⇒ 队列世代走 BLOCKING 车道(不带 --advisory 直接跑脚本)。

而脚本在 enforcing 模式下的语义(scripts/check-objectui-pin-fresh.mjs,已读):git ls-remote 单独决定判决,.objectui-sha != objectui main HEAD 即 staleexit 1。这正是本文件自述的「两次 pin bump 之间的常态」(WHERE IT BLOCKS 段:『Between pin bumps an ordinary code PR sits behind objectui almost always — that is the normal state of the repo, not a defect, and failing every PR over it would train everyone to ignore this check』)。

结论:只加触发器,会造出一个系统性飘红的队列检查;维护者一旦照邀请把它设为 required,红检查会把每个 PR 踢出队列 —— 那是把「永久 pending 死锁」换成「永久红死锁」,issue 要的「之后再加 required 才是安全的」并不成立。所以判据按文件既有的、已写死在注释里的政策显式化:

事件 车道 依据
workflow_dispatch BLOCKS 人手触发要完整判定(保持原行为)
merge_group 只报告 队列世代就是那个 PR 本身;发布 PR 由它自己的 pull_request run 把关,release.yml 还在发布路径上再跑一次同一脚本(无 override input)
pull_request 仅发布车道 BLOCKS 原行为

这不是新政策,是把文件里已经写下的政策补齐到新事件上。反过来说,若维护者认为队列世代应当 blocking,改法只是把 merge_group 挪进判据 —— 但那等于接受「pin 落后即全队飘红」,与该文件的设计自述冲突。

队列世代实际会做什么(逐条)

  • 跑什么:Self-test the gate(离线,零依赖)+ node scripts/check-objectui-pin-fresh.mjs --advisory
  • 网络:git ls-remote https://github.com/objectstack-ai/objectui.git main 定判决;仅在已判定 stale 时才发 3 个 GitHub API 调用去列举滞后内容(fresh 时零 API 调用)。
  • 队列安全性(核心问题):网络读不到 ⇒ 判决 unreadable;unreadableadvisory 模式下打 ::warning:: 并 return 0,只有 enforcing 模式才 exit 1。所以队列世代里的网络抖动/限流不会把 PR 踢出队列。这正是第 3 处改动买到的东西。
  • 已知取舍(维护者将来设为 required 时接受的那一条):发布 PR 自身的 pull_request run 判定之后、到它在队列里落地之间,若 objectui main 前进,队列世代不会再次拦截(它是 advisory)。兜底在 release.yml 的发布路径步骤 Console pin is fresh (#3340 — enforcing on the publish path) —— 那里直接跑脚本且刻意没有 override input。也就是说漏网窗口只在「发布 PR 排队的几分钟」,且被发布路径这道无法绕过的闸门覆盖。
  • 并发:concurrency.group${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }};merge_grouppull_request 为空,回落到 github.ref(每个队列世代唯一的 gh-readonly-queue/main/pr-N-SHA),世代之间不会互相取消。与 ci.yml 同一模式,无需改动。

验证

1. 车道映射 —— 直接执行文件里 shipped 的那段 shell(把两处 node scripts/... 换成 lane 标记,其余是原文):

改后(本 PR):

ordinary PR                  event=pull_request       -> advisory
release PR (branch match)    event=pull_request       -> blocking
release PR (title only)      event=pull_request       -> blocking
QUEUE generation             event=merge_group        -> advisory
manual dispatch              event=workflow_dispatch  -> blocking

反向验证(预测在先:用 origin/main 的原判据 —— 即「只加触发器」会 ship 的那个版本 —— 队列世代必然落到 blocking):

ordinary PR                  event=pull_request       -> advisory
release PR (branch match)    event=pull_request       -> blocking
release PR (title only)      event=pull_request       -> blocking
QUEUE generation             event=merge_group        -> blocking   ← 预测命中
manual dispatch              event=workflow_dispatch  -> blocking

唯一变化的格子就是 merge_group:blocking → advisory,四个既有车道逐字未变。

2. 门禁(worktree,pnpm install 后):

✓ check-workflow-status-functions --self-test: 34 assertions over temp fixture roots (real scan() path)
check-workflow-status-functions: OK (scanned 22 workflow file(s), 41 job(s), 24 job-level if: expression(s); 9 read needs.*.outputs.*, all naming a status function).

✓ check-nul-bytes --self-test: 56 assertions over a temp git repo (real scan() path)
check-nul-bytes: OK (scanned 5949 tracked text file(s); skipped 5 binary, 1 non-regular; no raw ASCII control bytes).

check-node-version: OK (25 setup-node step(s) across 22 workflow(s), all on Node 22).

3. YAML 与名字稳定性(python3 yaml.safe_load):

on: {"pull_request": {"branches": ["main"]}, "merge_group": null, "workflow_dispatch": null}
job ids: ['pin-freshness']
job name: Console Pin Freshness      ← context 名未变(将来进 required 集靠它)

4. workflow 首步与真实 advisory 跑通:

✓ check-objectui-pin-fresh --self-test: all checks passed
✓ objectui pin is FRESH — .objectui-sha is objectui `main` (objectui@7dfbeb704e1e).   exit=0

(顺带一条现场事实:此刻 pin 恰好等于 objectui main HEAD 7dfbeb70,所以今天即便走 blocking 车道也是绿的 —— 系统性飘红要等 objectui 下一次 merge。这恰恰说明只加触发器的做法会绿着上线、几小时后才开始咬人,更值得现在就把车道钉死。)

未做(刻意)


Generated by Claude Code

…y 车道 (#6121)

该 workflow 的注释明文邀请维护者把 `Console Pin Freshness` 加进 main 的
required 集,而它只订阅 pull_request + workflow_dispatch。合并队列在
gh-readonly-queue/** 世代上重新求值 required checks,一个在队列世代上不产生
check run 的 required context 会让每个世代永久等待(#3622 原型)。本仓队列是
活的(event=merge_group 共 2745 次运行,最近一批在今天 12:00 前后)。

三处改动,全部在同一文件内:

1. on: 增加裸键 merge_group:(与 ci.yml / lint.yml / spec-liveness-check.yml
   同族写法)。
2. 邀请注释改写为带前置条件的邀请:加入 required 集之所以安全,正是因为
   merge_group 触发器现在存在;后来的编辑者在保留/加入 required 前必须确认
   该触发器仍在。
3. 车道判据由 `[ "$EVENT" != "pull_request" ]` 改为按事件显式分类。这一处是
   加触发器的必要组成:该否定式在 on: 只有两项的年代等价于 workflow_dispatch,
   加入 merge_group 后会把每个队列世代扫进 BLOCKING 车道,而脚本在 enforcing
   模式下只要 pin != objectui main HEAD 就 exit 1 —— 即本文件自述的"两次 pin
   bump 之间的常态"。只加触发器会得到一个系统性飘红的队列检查,设为 required
   后每个 PR 都被踢出队列,等于把 pending 死锁换成红色死锁,issue 想要的
   "之后加 required 才安全"并不成立。新判据落实的是文件里已写下的既有政策
   (blocks only on the release lane),不是新政策。

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

vercel Bot commented Aug 7, 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 7, 2026 12:09pm

Request Review

@github-actions github-actions Bot added the size/s label Aug 7, 2026
@hotlong hotlong added skip-changeset PR has no user-facing published change; bypasses the changeset gate and removed size/s labels Aug 7, 2026 — with Claude
@github-actions github-actions Bot added the ci/cd label Aug 7, 2026
@hotlong
hotlong marked this pull request as ready for review August 7, 2026 12:29
@hotlong
hotlong added this pull request to the merge queue Aug 7, 2026
Merged via the queue into main with commit e4a03d2 Aug 7, 2026
27 of 28 checks passed
@hotlong
hotlong deleted the claude/issue-6121-pin-freshness-merge-group branch August 7, 2026 12:41
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