55 types : [opened, synchronize, reopened, labeled, unlabeled]
66
77jobs :
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
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