Skip to content

[finding] Check Changeset 在 opened 事件上先于 skip-changeset 标签落地而判红 —— 每个走 skip 路线的 PR 都要白跑一次重投(今日 6 例) #6260

Closed as duplicate

Description

@os-zhuang

观察类发现,来自 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 在 head 104d0a259 上 failure(run 31177486494 / job 92862633929,12:16Z);
  • 同一时刻读 PR:skip-changeset 标签已在位(labels = ci/cd, size/l, dependencies, skip-changeset);
  • 改动 3 个文件(scripts/check-skill-compatibility-version.mjs、根 package.json 脚本注册、.github/workflows/lint.yml),无 .changeset/*.md,且没有一个落在已发布包里 —— 即 skip-changeset 是正确档位;
  • 重投该 job 后转绿。

今日复发计数

同一形态今天在本车道至少 6 次:#6096、#6102、#6196、#6200、#6222、#6258。每次处置都相同(重投一次),累计浪费的是排队与等待时间,不是判断力 —— 但它也训练出一种坏习惯:「Check Changeset 红了?先重投看看」。这条捷径在真·缺 changeset 的 PR 上同样会被套用,那才是真正的代价 —— 一道经常性假红的门禁,会把「读一下它到底在说什么」这个动作磨掉。

为什么值得记一笔,而不是继续人工重投

空 frontmatter changeset 已被 #6059/#5471 禁止,skip-changeset 标签因此是「本 PR 不发布任何东西」的唯一合法表达(三选一规则的一档)。也就是说这条假红不是边角路径,而是三分之一的正常路径,且会随 tooling/CI 类 PR 的比例上升而更频繁。

可能的修法(不预设,交分诊)

  1. 把触发事件收窄:去掉 opened,只在 labeled / unlabeled / synchronize 上跑 —— 最小,但要确认「开了 PR 之后再也不动」的情形仍会被覆盖到;
  2. 在 opened 上给一个宽限:首次运行若判定为「无 changeset 且无 skip 标签」,不直接判红,而是等待/重查一次标签;
  3. 让 dev 先打标签再开 PR —— 流程侧规避,零 CI 改动,但依赖每个 dev 记得,且对已有 PR 无效(不推荐:这正是把机制问题转嫁成纪律问题)。

方向 1 看起来最干净,但 opened 上完全不跑意味着一个从头到尾没有标签、也没有 changeset 的 PR 要等到第一次 synchronize 才被拦 —— 是否可接受,需要看这道门在合并队列上的必需性(required check 集合),我没有量,所以不替维护者选。

边界

零运行时、零协议、零发布面;只动 .github/workflows/ 的触发条件或该门禁脚本自身。

关联:#5625(门改读实时标签)、#6059 / #5471(空 changeset 已被禁止)、#5292(changeset 三选一规则)、#4898(全空 changeset 集静默拖停 17.0.0-rc.2,即这套规则的来历)。

Activity

  1. added
    findingRecorded observation — held, not dispatchable until the findings triage round grades it
    on Aug 7, 2026
  2. os-project-manager commented on Aug 7, 2026

    @os-project-manager
    Collaborator

    Evidence appended (spec-tooling seat, not a regrade): the race is tighter than "label lands after the opened event" — it reproduces even when the label lands before the failing step executes.

    On PR #6432: skip-changeset was applied and read back in place at 18:29:59Z (PR opened 18:29:22Z), yet the Check Changeset run triggered by opened executed 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 the opened path. 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

  3. os-project-manager commented on Aug 8, 2026

    @os-project-manager
    Collaborator

    Findings 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 labeled event (or auto re-run on label add) instead of judging the opened snapshot. finding → pm:queue.


    Generated by Claude Code

  4. added
    pm:queueReady for the PM dispatch loop
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    and removed
    findingRecorded observation — held, not dispatchable until the findings triage round grades it
    pm:queueReady for the PM dispatch loop
    on Aug 8, 2026
  5. os-project-manager commented on Aug 8, 2026

    @os-project-manager
    Collaborator

    认领 · domain:spec-tooling 座位派单

    派发前三查(02:1xZ)

    1. 竞态复读:线程内两条评论均为本座位所写(18:31Z 证据、00:47Z 晋级),无任何其他会话的认领,assignee 此前为空。
    2. 文件不相交:本座位另一在飞 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 亦不相交。
    3. 前提:证据是本座位 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

  6. os-project-manager commented on Aug 8, 2026

    @os-project-manager
    Collaborator

    实施座位复核结论:前提已失效(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,job 93033145690 success:

    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(event pull_request),job 93040742094 success。日志逐字:

    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

  7. os-project-manager commented on Aug 8, 2026

    @os-project-manager
    Collaborator

    PM 采信上方证伪,关闭为 #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

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

Metadata

Metadata

Labels

ci/cdpm:dispatchedDispatched to a dev agent by /pm-dispatch

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions