Skip to content

fix(ci): 两个标签写入 job 不再并发、也不再被标签事件重复触发 (#5649) - #5683

Merged
os-zhuang merged 1 commit into
mainfrom
claude/issue-5649-label-additive-writes
Aug 6, 2026
Merged

fix(ci): 两个标签写入 job 不再并发、也不再被标签事件重复触发 (#5649)#5683
os-zhuang merged 1 commit into
mainfrom
claude/issue-5649-label-additive-writes

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

Fixes #5649

本 PR 只做 issue 的落点 2(CI 侧)。落点 1(客户端写入语义)不改代码,实测结论见下方「工具面备案」。

一、归因:两个 job 都在整集 PUT,sync-labels 不是那个开关

按派发要求逐个读 pinned 版本的源码,不接受配置文档推断:

action 写入位置 语义
codelytv/pr-size-labeler@v1.10.4 src/github.sh:68-91(github::add_label_to_pr) GET 读 PR 标签 → grep -w -v 掉自己那套 size 家族 → 追加新 size 标签 → curl -X PUT .../issues/$pr_number/labels
actions/labeler@v7.0.0 src/labeler.ts:56,111-133 + src/api/set-labels.ts run 开始时快照 preexistingLabels → 并入 config 命中项 → 回读一次实时标签(pulls.get,补 manualAddedDuringRun)→ client.rest.issues.setLabels,即 PUT

两者都是整集 PUT,都没有把写入改成新增的输入项。 sync-labels 不是那个开关:它只决定「config 自己拥有的标签在 glob 不再命中时是否删掉」(labeler.ts:81-83),对外来标签毫无作用,而且 action.yml 里缺省已经是 false。本 PR 仍然把它显式写出来,理由只有一条 —— 防升级漂移;注释里已经写明它不是本缺陷的修复,免得下一个读者把它当成已修。

谁抹的:run/job 级对号(PR #5650)

opened run 31051251795:

Check PR Size / Add size label                22:03:47 -> 22:03:49   → labeled   size/s        22:03:48
Auto Label    / Label based on changed files  22:03:47 -> 22:03:49   → unlabeled size/s        22:03:49
                                                                       labeled   documentation 22:03:49

两个写入步骤的执行窗口完全重合;22:03:49 这条 unlabeled size/slabeled documentation 同秒同 actor,只有 labeler 的一次 PUT {documentation} 能同时解释两者 —— 它的快照与回读都早于 22:03:48。方向二的抹除者是 Auto Label(actions/labeler)。

同一判据套回 #5533 的 15:46:45:unlabeled skip-changeset + labeled size/m + labeled tests 三条同秒,只有 labeler 的一次 PUT {size/m, tests} 能同时解释(size-labeler 的写入会保留 skip-changeset、也不会加 tests)。同一个抹除者。

二、改了什么(只动这两个 job 的配置)

整集 PUT 只在「别人的写入落在读→PUT 窗口内」时才有破坏性。本文件能修的是重叠:

  1. 两个写入方不再并发 —— auto-labelneeds: pr-size。原先由同一事件同时拉起、窗口重合(上表)。串行后 labeler 的快照里已经有 size 标签,PUT 会把它带上。!cancelled() 显式写出:needs 只用于排序,size job 失败/跳过不该顺手关掉路径打标。
  2. 两个写入方不再被 labeled/unlabeled 触发 —— 它们唯一的输入是 diff,标签事件改不了 diff,这种 run 只能把同一集合再 PUT 一遍:零新信息,多一次互抹机会。现场标本:同 PR 的 run 31051273625 由标签事件拉起,Auto Label 重算后无写入(labeler.ts:111isEqual 短路),Check PR Size 在 22:04:22 又 PUT 了一次。两个事件类型保留在 on:,因为 changeset-check 确实需要(Check Changeset 从事件载荷读 skip-changeset 标签:开 PR 后 5 秒内加标签,首个 run 永久红(重跑复用载荷)—— 一日三例 #5580)。
    全仓 grep 确认 size/*documentationtests 没有任何门禁/脚本消费,只有 .github/labeler.yml 与本文件提到它们 —— 所以取消「标签事件驱动的自动补挂」不会让任何判据失去输入。

⛔ 未触碰:changeset-check 的实时读与计数逻辑(#5580/#5625)、allow-major(#5620)、.github/labeler.yml、MCP 服务端。

三、验证

  • node scripts/check-workflow-status-functions.mjsOK (scanned 22 workflow file(s), 39 job(s), 21 job-level if: expression(s); 9 read needs.*.outputs.*, all naming a status function);--self-test → 34 assertions 全过。
  • YAML 结构验证(真 YAML parse,非 grep):pr-sizeneedsif 为两个事件排除;auto-label needs=pr-sizeif!cancelled();changeset-checkif 与 main 逐字一致;labeler step with = {repo-token, configuration-path, sync-labels: false}
  • node scripts/check-nul-bytes.mjsOK (scanned 5600 tracked text file(s) ...);另按控制字符条款做了闸门盲区自扫 grep -naP '[\x00-\x08\x0b\x0c\x0e-\x1f]',零命中。
  • 生效 ref:pull_request 事件的 workflow 定义取自 merge ref,所以本 PR 的改动在本 PR 自己的 run 上就生效,A/B 可直接看本 PR 的 timeline。labeler 的配置文件同样取自 merge ref —— src/api/get-content.tsref: github.context.sha,pull_request 下即 merge commit(本 PR 没改 labeler.yml,但派发问到了,一并记为源码结论)。
  • 可核验判据(改动前后对照):本 PR 的 timeline 中,github-actions[bot] 不应再出现「针对本 job 不管的标签的 unlabeled」;Check PR SizeAuto Label 的执行窗口不再重合(后者 started_at 晚于前者 completed_at);标签事件不再拉起这两个 job(对应 run 里它们为 skipped)。

四、未关闭的一半(留给维护者决策,本 PR 不擅自动依赖)

工作流之外的写入方 —— 例如 gh pr create 之后几秒挂 skip-changeset 的 agent 或人,正好落在这两个 job 运行期间 —— 仍可能落进 PUT 窗口被抹。#5533 的豁免标签只活一秒就是这样丢的(15:46:44 挂上,15:46:45 被 labeler 的 PUT 抹掉),而这正是本 issue 里有实际代价的那一半。它需要写入本身变成新增语义,不是排序问题;两个 action 都没有对应的配置开关,只剩「自己拥有写入路径 / 换 action」这类决策。已按报告契约上报给 PM,注释里也写明了边界,没有伪装成已修。

五、工具面备案(落点 1,不改代码)

MCP issue_writelabels 字段语义会在本 PR 上做一次可控实验(挂标签前后回读),结论写进 #5649 的评论。agent 指令面「只加一个标签、不要整集写入」的条款已由 #5559 / PR #5650 落地,本单只补工具面事实。

Changeset

workflow-only、无用户可见变更 → 走 skip-changeset 标签路线,不写空 frontmatter changeset(#4898 / #5292)。


Generated by Claude Code

`Check PR Size` 与 `Auto Label` 都用**整集 PUT**(`PUT /issues/{n}/labels`)
写标签,不是新增 POST。源码实测(非文档推断):

- `codelytv/pr-size-labeler@v1.10.4` `src/github.sh:68-91`:GET 读 PR 标签,
  grep 掉自己那套 size 家族,追加新 size 标签,然后 `curl -X PUT`。
- `actions/labeler@v7.0.0` `src/labeler.ts:56,111-133` + `src/api/set-labels.ts`:
  run 开始时快照 `preexistingLabels`,并入 config 命中项,回读一次实时标签,
  再 `client.rest.issues.setLabels` —— 就是 PUT。

两者都没有把写入改成新增的输入项;`sync-labels` 也不是那个开关 —— 它只管
「config 自己拥有的标签在 glob 不再命中时是否删掉」(`labeler.ts:81-83`),
缺省已是 false。本 PR 仍把它显式写出,只为防升级漂移,不是本缺陷的修复。

整集 PUT 只在「别人的写入落在读→PUT 窗口内」时才有破坏性,所以本文件能修的是
**重叠**:

1. 两个写入方不再并发(`auto-label` needs `pr-size`)。原先由同一事件同时拉起、
   窗口完全重合:PR #5650 run 31051251795(`opened`)里 `Add size label`
   22:03:47->22:03:49、`Label based on changed files` 22:03:47->22:03:49,
   labeler 的 PUT 在 22:03:49 发出 `unlabeled size/s` —— 抹掉的是它不管的标签。
2. 两个写入方不再被 `labeled`/`unlabeled` 触发。它们唯一的输入是 diff,标签事件
   改不了 diff,这种 run 只能把同一个集合再 PUT 一遍 —— 零新信息,多一次互抹
   机会。同 PR run 31051273625(由标签事件拉起):`Auto Label` 重算后没写,
   `Check PR Size` 在 22:04:22 又 PUT 了一次。两个事件类型保留在 `on:` 里,
   因为 `changeset-check` 确实需要(#5580)。

未被本 PR 关闭、并写进文件注释而非留给下一个读者踩:**工作流之外**的写入方
(`gh pr create` 后几秒挂标签的 agent 或人,正好落在这两个 job 运行期间)
仍可能落进 PUT 窗口被抹 —— #5533 的 `skip-changeset` 豁免只活一秒就是这样丢的
(15:46:44 挂上,15:46:45 被 labeler 的 `{size/m, tests}` PUT 抹掉)。关掉这半
需要写入本身变成新增语义,不是排序问题,任何本文件的配置都替代不了。

`changeset-check` 的实时读与计数逻辑(#5580/#5625)、`allow-major`(#5620)一字未动。
@vercel

vercel Bot commented Aug 6, 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 6, 2026 12:05am

Request Review

@os-zhuang os-zhuang added skip-changeset PR has no user-facing published change; bypasses the changeset gate ci/cd size/s and removed ci/cd size/s labels Aug 6, 2026 — with Claude
@os-zhuang
os-zhuang marked this pull request as ready for review August 6, 2026 00:13
@os-zhuang
os-zhuang added this pull request to the merge queue Aug 6, 2026
Merged via the queue into main with commit 9fdb2f6 Aug 6, 2026
38 of 40 checks passed
@os-zhuang
os-zhuang deleted the claude/issue-5649-label-additive-writes branch August 6, 2026 00:25
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Aug 6, 2026
…tack-ai#5684) (objectstack-ai#5687)

objectstack-ai#5650 落地的措辞是「**Add** the one label instead of writing the label set」。
意图正确,但 os-dev 手上唯一能写标签的工具(MCP `issue_write` 的 `labels`)是
整集 PUT——照字面执行 `labels: ['skip-changeset']` 恰好就是该句要禁止的整集
抹除(objectstack-ai#5683 受控实验:同秒两条 unlabeled + 一条 labeled;写并集则零 unlabeled)。

改为可执行处方:先回读当前标签集 → 写并集(现有集 + skip-changeset)→ 写后
回读确认(原有闭环保留),并记入工具面事实:`POST /issues/{n}/labels` 不可达
(容器无 gh CLI、匿名 curl 不可写),回读须走 REST `GET /pulls/{n}`,
`issue_read get_labels` 解析不了 PR 号。

同句内两处把写入称作 POST 的措辞随之改为 write——同一条款内的命名一致性,
否则条款一边说唯一写法是整集 PUT、一边让读者去找一条不可达的 POST。
条款其余要素(硬步骤地位、Auto Label 不会代挂、双向竞态预期、回读进报告)
未动。

Co-authored-by: os-zhuang <hr@objectstack.ai>
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

2 participants