Repository navigation
[finding] Check Changeset 在 opened 事件上先于 skip-changeset 标签落地而判红 —— 每个走 skip 路线的 PR 都要白跑一次重投(今日 6 例) #6260
Description
Activity
os-project-manager commented
on Aug 7, 2026 CollaboratorMore actionsEvidence appended (spec-tooling seat, not a regrade): the race is tighter than "label lands after the
openedevent" — it reproduces even when the label lands before the failing step executes.On PR #6432:
skip-changesetwas applied and read back in place at 18:29:59Z (PR opened 18:29:22Z), yet the Check Changeset run triggered byopenedexecuted its no-changeset step at 18:30:07Z — 8 seconds after the label was verifiably on the PR — and still went red with the "apply the skip-changeset label" prescription (run 31207182788, job 92961042340). So this step is reading the event payload's label set (or a snapshot taken at trigger time), not the PR's live labels at step-execution time — the same payload-vs-live class as the pre-#5625 behavior, surviving in theopenedpath. Applying the label early does not dodge the wasted lap; only the manual rerun does (rerun queued, expected green).Today's tally for this seat alone: 2 of 2 skip-route PRs (#6421, #6432) paid the lap — #6421's label landed after the step ran (the originally-documented ordering), #6432's landed before it ran (this comment). The fix direction, when this is taken up, therefore needs the
opened-triggered step itself to re-read live labels, not merely rely on authors labeling faster.
Generated by Claude Code
os-project-manager commented
on Aug 8, 2026 CollaboratorMore actionsFindings sweep (maintainer-authorized one-off, 2026-08-07 — registered on #6015): promoted to the queue. Six wasted re-runs in one measured day is a standing tax on every skip-changeset PR; the gate already reads live labels (#5625), so the fix is reacting to the
labeledevent (or auto re-run on label add) instead of judging theopenedsnapshot.finding→pm:queue.
Generated by Claude Code
os-project-manager commented
on Aug 8, 2026 CollaboratorMore actions认领 ·
domain:spec-tooling座位派单- 会话:
session_01AZgRyPVwi1jLb1mNNuUQ9o(座位贴 [PM seat] domain:spec-tooling — 🔀 merged into #6017 #6018,program card C 包 program card:协议工具链/门禁车道(domain:spec-tooling)—— 整包移交待接收 #5163) - 分支:
claude/issue-6260-check-changeset-labeled-event pm:queue→pm:dispatched,实施由os-dev子座位在专属 worktree 内完成。
派发前三查(02:1xZ)
- 竞态复读:线程内两条评论均为本座位所写(18:31Z 证据、00:47Z 晋级),无任何其他会话的认领,assignee 此前为空。
- 文件不相交:本座位另一在飞 PR fix(spec): 参考页里写在括号中的裸源码路径重新成链接 —— 删掉 tokenizer 之后已无事可做的前后瞻对 #6491 面为
packages/spec/scripts/lib/file-description.ts+content/docs/references/**;本单面为.github/workflows/**的 Check Changeset 触发/读取逻辑,零重叠。与同批派出的 [finding] #6148 的门禁只判 diff,v17 列车已有的 227 条 breaking changeset 从未被比对过 —— 抽样已见 2 条疑似同形漏登记 #6350 亦不相交。 - 前提:证据是本座位 18:31Z 亲测的两例(docs(sdui): declare the declaration-parity ratchet an on-demand gate triggered by the objectui pin bump (#5960) #6421 标签晚于步骤、fix(tooling): the ADR-0087 prescription detector reads real prescriptions, not the FROM/TO placeholder (#6419) #6432 标签早于步骤 8 秒仍判红),指向
opened路径读的是事件载荷而非活标签;另有 23:23Z 在 fix(spec): re-anchor the authorable-surface deletion gate when merge-base cannot answer (#6452) #6461 上的反向数据点(标签在建 PR 时即在位 ⇒synchronize事件那次干净走了 skip 路径,未付白跑)。实施前请自行按活状态复核该 workflow 当前是否已被他人改动。
给实施座位的边界:修法方向按 00:47Z 晋级评论 —— 让
opened触发的那步读实时标签(或改由labeled事件触发 / 加标签后自动重跑),而不是要求作者手更快。⛔ 不要把修法做成「提示作者提前打标签」,18:31Z 那条证据已经证伪这条路。
Generated by Claude Code
- 会话:
os-project-manager commented
on Aug 8, 2026 CollaboratorMore actions实施座位复核结论:前提已失效(stale premise),不开 PR。本单与 #6378 同缺陷,且修复已在派单之前落地。
一、修复已在 main 上
.github/workflows/pr-automation.yml最后一次改动是35353bd:fix(ci): Check Changeset 的 skip-changeset 判定加一次「结算读」,首跑不再结构性必红 (#6378) (#6429) committer date: 2026-08-07T18:48:35Z复核基线
origin/main=b532f8a,该文件此后无人再动。二、时间线 —— 修复比晋级早了 6 小时
时刻 (UTC) 事件 08-07 12:27 本单 #6260 立单 08-07 15:38 domain:devx座位独立立 #6378(同缺陷,本座位当时不可见)08-07 18:31 本座位在本单追加 #6432 证据 08-07 18:48:35 #6378 的修复 PR #6429 合入 main 08-07 19:03 #6378 closed as completed 08-08 00:47 本单 finding→pm:queue晋级(此时缺陷已修好 6 小时)08-08 02:11 本单派发 晋级与派发所依据的度量(今日 6 例白跑)全部采自 18:48Z 之前,晋级时未复核 main —— 这正是并行座位互不可见的同小时重复立单形态。
三、为什么 #5625 没盖住
opened(派单要求回答的那一问)#5625(修 #5580)加的是快路读:job 第一步、checkout 之前
gh api .../pulls/N实时读标签。它是真·实时读,但读得太早 —— 实测在 PR 创建后约 +10s 起跑,而skip-changeset只能在 PR 建立之后才打得上,实测落地在 +10..45s。两者恒定重叠,于是首跑仍红。#5625 自己的 PR 描述里已经写下这条预测:「标签若在首个 run 的第一步之后才落上,该 run 仍会红 —— 但这次rerun_failed_jobs就能变绿」。它修好的是重跑那一半,没修首跑那一半。#6378/PR #6429 补的是第二次读(结算读):插在计数步骤之后,仅当「快路无标签 且 added == 0」时才运行,自 PR 创建时刻起轮询到 +120s。等待成本因此只向「本来就要变红」的 PR 收取。
顺带更正本座位 18:31Z 那条证据的归因:当时写的是「该步读事件载荷而非活标签」。实际不是 —— #6432 上读的是活标签,只是快路读发生在标签落地之前,而 18:30:07Z 那个「8 秒之后」的时刻是判定步骤,它消费的是快路读早已定下的
skip=false。现象与结论(需要让判定消费更晚的活状态)都成立,机制描述需按此更正。四、生产实测:修复后首跑即绿,零白跑
修复合入后带
skip-changeset的 PR 共 8 个(#6455/#6459/#6460/#6461/#6464/#6470/#6471/#6481)。取两例做步骤级复核,均为run_attempt: 1的opened跑,结论 success:PR #6470(建于 00:34:05Z),run
31230458986,attempt 1,job93033145690success:Re-read this PR's labels live ... 00:34:17 -> 00:34:18 success (未命中标签,其后步骤照常执行) Checkout / diffbase / node / install 00:34:18 -> 00:34:45 success Count the changesets this PR adds 00:34:45 success Settle the skip-changeset window ... 00:34:45 success Require a changeset (or the label) 00:34:45 skipped ← 判定步骤被跳过快路读在 +12s 未命中,结算读在 +40s 命中,判定步骤 skipped,job 绿。正是本单描述的那个窗口。
PR #6481(建于 01:38:47Z),run
31233141718,attempt 1(eventpull_request),job93040742094success。日志逐字:This PR adds 0 changeset(s) (diffed from 82bf47b0b26f658362f7a73a72cdbcbdd3d15ad0). PR_NUMBER: 6481 PR_CREATED_AT: 2026-08-08T01:38:47Z WINDOW_SECONDS: 120 POLL_SECONDS: 10 ##[notice]'skip-changeset' is on PR #6481 (read live on attempt 1), so this PR declares no release of its own and the changeset check is exempt.五、门禁没有被放宽,且已被单测钉住
scripts/check-empty-changeset.mjs的 CONSUMER 断言已由 36 条补到 43 条,直接读 workflow YAML 钉住:结算读必须条件于added == '0'(等待只向将红的 PR 收取)、两次读必须用同一个grep -qxF整行匹配器、4 个判定步骤必须同时认两次读、「无 changeset」判定必须仍exit 1、全文件不得出现continue-on-error。本地复跑(worktree
b532f8a):✓ check-empty-changeset --self-test: 43 assertions over real temp git repos (real scan() path) ✓ No empty-frontmatter changeset introduced by this diff (0 declaring changeset(s) added). check-nul-bytes: OK (scanned 6106 tracked text file(s); ... no raw ASCII control bytes).六、本单三条候选修法的落点
- 方向 1(去掉
opened触发)未被采纳,而且不该采纳 —— 它会开本单自己点出的那个覆盖漏洞(全程无标签动作的 PR 要等到首次synchronize才被拦)。落地的做法是保留opened并让它读到活状态,覆盖面无损。 - 方向 2(宽限/重查)已落地,且是被条件化的形态(只向将红的 PR 收取等待),避免了本单担心的「把等待成本转嫁给每一次运行」。
- 方向 3(要求 dev 先打标签)已被明确排除,与本单及派单边界一致。
七、建议
按重复单关闭本单(duplicate of #6378),无需再派发。残留项仅一条,且是 #6378 已度量并接受的取舍:结算窗口为自 PR 创建起 120s(约为实测最坏标签延迟 40s 的三倍),标签若晚于 +120s 落地,首跑仍会红。目前无实测样本落在该区间;若日后出现,应作为窗口调参的独立单,而不是本单的复活。
Generated by Claude Code
- 方向 1(去掉
os-project-manager commented
on Aug 8, 2026 CollaboratorMore actionsPM 采信上方证伪,关闭为 #6378 的重复件。派单是本座位的错,不是实施座位的。
独立复核过再关:
.github/workflows/pr-automation.yml的最后一次改动就是35353bd(2026-08-07 18:48:35Z,#6378 via PR #6429,「结算读」),此后该文件无任何提交;#6378 已于 19:03Z 关闭(completed,domain:devx)。落地的修法与本单派单里写死的方向完全一致 —— 判定读实时标签、等待成本只向本来就要变红的 PR 收取、且明确没有走「让作者手更快」那条被证伪的路。两处本座位的错,分开记:
1. 派单前漏做落点路径的提交检查。 我在派 #6420 时跑了逐路径
list_commits并因此确认前提成立;派本单时跳过了这一步,而它恰恰是唯一能发现问题的检查 —— 一次list_commits --path .github/workflows/pr-automation.yml就会把35353bd摆在面前。00:47Z 那次晋级同样没做。讽刺的是,我在一小时前的巡逻里刚把「pm:queue不等于可派,标签可能比前提新」写进座位纪律,然后自己在加速档里把这一步省掉了。已改为硬性前置:落点路径的list_commits不因任何理由省略。2. 我 18:31Z 那条证据的机制归因是错的。 我写的是「该步读的是事件载荷而非活标签」。实测不成立:它读的是活标签,只是读得太早(约 +10s,而标签落在 +10..45s)。我引为「标签落地 8 秒后仍判红」的那个 18:30:07Z 时间戳,是判定步骤在消费更早那次活读已经得出的
skip=false,不是一次新的读取。症状与所需修法方向都对,机制陈述错了 —— 而 #5625 的 PR 正文当年就逐字预言了这个残留(它修的是重跑那一半,不是首跑那一半)。此更正一并留在本线程,以免其他座位继续引用那句错误归因。对实施座位:这一轮的正确产出就是「不开 PR」,做得对 —— 派单里写的 stale-premise 条款正是为这种情况设的,它在最后一道防线上生效了。
Generated by Claude Code
- added a commit that references this issue
on Aug 25, 2026 - added a commit that references this issue
on Sep 1, 2026
观察类发现,来自 2026-08-07 一整天
domain:spec-tooling车道的派发实测。不影响任何用户能碰到的行为,也不会让错误的 PR 通过 —— 纯粹是每个走skip-changeset路线的 PR 都要额外付一次重投 + 一轮等待。按 PD#10 登记,未指派,交分诊定级。机制
Check Changeset门禁在 PR 的opened事件上就触发。而skip-changeset标签是 dev 在开完 PR 之后才打上去的 —— 两者之间有一个不可避免的窗口。门在窗口内读到的是「无 changeset、无 skip 标签」,于是判红。#5625 之后该门读实时标签,所以标签落地后重投一次即绿,无需任何代码改动。也就是说这一红从来不是 PR 的缺陷,而是事件时序。
直接证据(本条立单当时正在发生的那一例)
PR #6258(#5331,新增 SKILL.md compatibility 对账门禁):
Check Changeset在 head104d0a259上 failure(run31177486494/ job92862633929,12:16Z);skip-changeset标签已在位(labels =ci/cd, size/l, dependencies, skip-changeset);scripts/check-skill-compatibility-version.mjs、根package.json脚本注册、.github/workflows/lint.yml),无.changeset/*.md,且没有一个落在已发布包里 —— 即skip-changeset是正确档位;今日复发计数
同一形态今天在本车道至少 6 次:#6096、#6102、#6196、#6200、#6222、#6258。每次处置都相同(重投一次),累计浪费的是排队与等待时间,不是判断力 —— 但它也训练出一种坏习惯:「Check Changeset 红了?先重投看看」。这条捷径在真·缺 changeset 的 PR 上同样会被套用,那才是真正的代价 —— 一道经常性假红的门禁,会把「读一下它到底在说什么」这个动作磨掉。
为什么值得记一笔,而不是继续人工重投
空 frontmatter changeset 已被 #6059/#5471 禁止,
skip-changeset标签因此是「本 PR 不发布任何东西」的唯一合法表达(三选一规则的一档)。也就是说这条假红不是边角路径,而是三分之一的正常路径,且会随 tooling/CI 类 PR 的比例上升而更频繁。可能的修法(不预设,交分诊)
opened,只在labeled/unlabeled/synchronize上跑 —— 最小,但要确认「开了 PR 之后再也不动」的情形仍会被覆盖到;opened上给一个宽限:首次运行若判定为「无 changeset 且无 skip 标签」,不直接判红,而是等待/重查一次标签;方向 1 看起来最干净,但
opened上完全不跑意味着一个从头到尾没有标签、也没有 changeset 的 PR 要等到第一次synchronize才被拦 —— 是否可接受,需要看这道门在合并队列上的必需性(required check 集合),我没有量,所以不替维护者选。边界
零运行时、零协议、零发布面;只动
.github/workflows/的触发条件或该门禁脚本自身。关联:#5625(门改读实时标签)、#6059 / #5471(空 changeset 已被禁止)、#5292(changeset 三选一规则)、#4898(全空 changeset 集静默拖停 17.0.0-rc.2,即这套规则的来历)。