Skip to content

Commit 9fdb2f6

Browse files
os-zhuangclaude
andauthored
fix(ci): 两个标签写入 job 不再并发、也不再被标签事件重复触发 (#5649) (#5683)
`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)一字未动。 Co-authored-by: Claude <noreply@anthropic.com>
1 parent 77022a9 commit 9fdb2f6

1 file changed

Lines changed: 72 additions & 0 deletions

File tree

.github/workflows/pr-automation.yml

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,59 @@ on:
55
types: [opened, synchronize, reopened, labeled, unlabeled]
66

77
jobs:
8+
# ===========================================================================
9+
# Both label-writing jobs below write this PR's label set with a WHOLE-SET PUT
10+
# (`PUT /issues/{n}/labels`), never an additive POST. Read out of the pinned
11+
# sources rather than inferred from the docs (#5649):
12+
#
13+
# * codelytv/pr-size-labeler@v1.10.4 -- src/github.sh:68-91
14+
# (`github::add_label_to_pr`): GETs the PR, greps its OWN size family out
15+
# of the result, appends the new size label, then
16+
# `curl -X PUT .../issues/$pr_number/labels` with the whole set.
17+
# * actions/labeler@v7.0.0 -- src/labeler.ts:56,111-133 plus
18+
# src/api/set-labels.ts: snapshots `preexistingLabels` at run start,
19+
# unions in the config matches, re-reads the live label list once, then
20+
# calls `client.rest.issues.setLabels` -- which IS the PUT.
21+
#
22+
# Neither action exposes an input that makes its write additive, and
23+
# `sync-labels` is NOT that input: it only decides whether a label the CONFIG
24+
# owns is dropped once its globs stop matching (labeler.ts:81-83). It is
25+
# pinned explicitly below for upgrade-drift protection only. It does not, and
26+
# cannot, stop the clobbering described here.
27+
#
28+
# A whole-set PUT only destroys someone else's label when that label lands
29+
# inside the window between the writer's read and its PUT. What this file can
30+
# therefore fix is the OVERLAP, and two changes below do exactly that:
31+
#
32+
# 1. The two writers no longer run concurrently -- `auto-label` needs
33+
# `pr-size`. They used to be started by the same event and overlapped
34+
# exactly. Live specimen, PR #5650 run 31051251795 (the `opened` run):
35+
# `Add size label` ran 22:03:47->22:03:49 and
36+
# `Label based on changed files` ran 22:03:47->22:03:49, and the
37+
# labeler's PUT emitted `unlabeled size/s` at 22:03:49 -- one second
38+
# after the size job added it, for a label the labeler does not manage.
39+
# 2. Neither writer runs on `labeled`/`unlabeled` any more. Their only input
40+
# is the diff, which a label event cannot change, so such a run could
41+
# only ever re-PUT the same set -- one more chance to erase a concurrent
42+
# writer in exchange for no new information. Same PR, run 31051273625
43+
# (started by a label event): `Auto Label` recomputed and wrote nothing,
44+
# `Check PR Size` re-PUT at 22:04:22. The two event types stay in `on:`
45+
# because `changeset-check` genuinely needs them (#5580).
46+
#
47+
# NOT closed by either change, and deliberately recorded rather than implied:
48+
# a writer OUTSIDE this workflow -- an agent or a human labelling the PR
49+
# seconds after `gh pr create`, i.e. exactly while these jobs run -- can still
50+
# land inside a PUT window and be erased. That is how #5533 lost its
51+
# `skip-changeset` exemption for one second (15:46:44 applied, 15:46:45 erased
52+
# by the labeler's PUT of `{size/m, tests}`). Closing that half needs the
53+
# writes themselves to become additive, not merely better ordered; it is the
54+
# open half of #5649 and no configuration here can stand in for it.
55+
# ===========================================================================
856
pr-size:
957
name: Check PR Size
58+
# A `labeled`/`unlabeled` event cannot change this job's input (the diff),
59+
# so running it there buys nothing and costs one whole-set PUT. See above.
60+
if: github.event.action != 'labeled' && github.event.action != 'unlabeled'
1061
runs-on: ubuntu-latest
1162
permissions:
1263
pull-requests: write
@@ -31,6 +82,19 @@ jobs:
3182

3283
auto-label:
3384
name: Auto Label
85+
# ORDERING ONLY, not a dependency: this job wants `pr-size`'s PUT to be
86+
# already done, so that the label set this one reads includes the size
87+
# label and its own PUT carries it forward. `!cancelled()` is written out
88+
# because GitHub would otherwise wrap this `if:` in an implicit `success()`
89+
# -- a failed or skipped size job must not silently stop path labelling.
90+
# (Same reasoning the check-workflow-status-functions gate exists to make
91+
# explicit; that gate scans only `needs.*.outputs.*` reads, so this one is
92+
# out of its scope and has to state its intent by hand.)
93+
needs: pr-size
94+
if: >-
95+
!cancelled()
96+
&& github.event.action != 'labeled'
97+
&& github.event.action != 'unlabeled'
3498
runs-on: ubuntu-latest
3599
permissions:
36100
contents: read
@@ -45,6 +109,14 @@ jobs:
45109
with:
46110
repo-token: ${{ secrets.GITHUB_TOKEN }}
47111
configuration-path: .github/labeler.yml
112+
# Pinned at the value it already defaults to (action.yml), because a
113+
# default is not a decision: an upgrade may move it, and `true` would
114+
# make this step REMOVE a label of its own config whenever the globs
115+
# stop matching -- on a `synchronize` that reverts a docs file, for
116+
# instance. Pinning it is upgrade-drift protection and nothing more:
117+
# `sync-labels` never governed foreign labels, so it is NOT the fix
118+
# for the clobbering documented at the top of this file (#5649).
119+
sync-labels: false
48120

49121
changeset-check:
50122
name: Check Changeset

0 commit comments

Comments
 (0)