You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
echo "::warning::No PR number on this event, so the label window cannot be settled. Enforcing the changeset check."
422
+
echo 'skip=false' >> "$GITHUB_OUTPUT"
423
+
exit 0
424
+
fi
425
+
# An absent or unparseable created_at collapses the deadline to "now":
426
+
# one read, no wait. That is the enforcing direction and it matches
427
+
# every other unreadable input in this job (#4690).
428
+
DEADLINE=0
429
+
if [ -n "$PR_CREATED_AT" ] && CREATED=$(date -u -d "$PR_CREATED_AT" +%s 2>/dev/null); then
430
+
DEADLINE=$((CREATED + WINDOW_SECONDS))
431
+
else
432
+
echo "::warning::This event carries no readable pull_request.created_at, so the label window is treated as already closed: one read, no wait."
433
+
fi
434
+
ATTEMPT=0
435
+
while :; do
436
+
ATTEMPT=$((ATTEMPT + 1))
437
+
if ! LABELS=$(gh api "repos/$GITHUB_REPOSITORY/pulls/$PR_NUMBER" --jq '.labels[].name'); then
438
+
echo "::warning::Could not read the labels of PR #$PR_NUMBER (attempt $ATTEMPT), so this run cannot see a 'skip-changeset' applied after the event fired. Enforcing the changeset check."
439
+
echo 'skip=false' >> "$GITHUB_OUTPUT"
440
+
exit 0
441
+
fi
442
+
# Whole-line fixed match, fed by a here-string -- deliberately the
443
+
# SAME matcher as the fast-path read above, for the same two reasons
444
+
# (an array-element match, not a substring, so `skip-changeset-audit`
445
+
# is not newly exempted; and `grep -q` kept out of a pipeline so no
446
+
# writer can take SIGPIPE under `set -o pipefail`). A divergence
447
+
# between the two reads would be a gate that exempts on one path and
448
+
# enforces on the other, so check-empty-changeset.mjs pins them equal.
449
+
if grep -qxF 'skip-changeset' <<<"$LABELS"; then
450
+
echo "::notice::'skip-changeset' is on PR #$PR_NUMBER (read live on attempt $ATTEMPT), so this PR declares no release of its own and the changeset check is exempt."
451
+
echo 'skip=true' >> "$GITHUB_OUTPUT"
452
+
exit 0
453
+
fi
454
+
NOW=$(date -u +%s)
455
+
# The attempt cap is belt-and-braces next to the deadline: a clock
456
+
# that disagrees with GitHub's must not be able to make this loop
457
+
# unbounded.
458
+
if [ "$NOW" -ge "$DEADLINE" ] || [ "$ATTEMPT" -ge 20 ]; then
459
+
break
460
+
fi
461
+
echo "No 'skip-changeset' on PR #$PR_NUMBER yet (attempt $ATTEMPT); it is still $((DEADLINE - NOW))s inside the label window. Re-reading in ${POLL_SECONDS}s."
462
+
sleep "$POLL_SECONDS"
463
+
done
464
+
echo "Label window closed after $ATTEMPT read(s). Labels on PR #$PR_NUMBER right now: ${LABELS:-(none)}"
465
+
echo 'skip=false' >> "$GITHUB_OUTPUT"
466
+
467
+
# The VERDICT. Everything it needs was decided above; this step only
468
+
# announces it, which is what makes the failure message a single block of
469
+
# prose rather than something interleaved with counting and polling.
470
+
- name: Require a changeset (or the skip-changeset label)
471
+
if: >-
472
+
steps.labels.outputs.skip != 'true'
473
+
&& steps.labels_settled.outputs.skip != 'true'
474
+
env:
475
+
ADDED: ${{ steps.changeset_count.outputs.added }}
476
+
run: |
477
+
if [ "$ADDED" = 'n/a' ]; then
478
+
echo "There is no .changeset directory to count; the counting step above said so and this gate stands aside."
479
+
exit 0
480
+
fi
344
481
if [ "$ADDED" -eq 0 ]; then
345
482
# The full comparison goes to the job log — that is what an author
346
483
# reading `gh run view --log-failed`, or expanding this step in the
@@ -362,6 +499,13 @@ jobs:
362
499
The label is a gate-level exemption. It produces NO input for
363
500
changesets/action, so it cannot affect a release.
364
501
502
+
You do not have to race this run to apply it: the step above waits
503
+
out a window measured from PR creation before this verdict is
504
+
reached (#6378), so a label applied promptly after 'gh pr create'
505
+
is seen by THIS run. If you were slower than that, applying it now
506
+
still fires a 'labeled' event and that run goes green -- but the
507
+
red left here does not clear itself.
508
+
365
509
'skills/**' is on that list, and it is spelled out because the git
366
510
log says otherwise (#5947). Changes to PUBLISHED skills have
367
511
repeatedly shipped with an empty changeset instead -- #4607, #5130,
@@ -373,8 +517,9 @@ jobs:
373
517
EITHER, and pays #4898 for the privilege. Take the label.
374
518
375
519
3. (CLOSED) An empty-frontmatter changeset. Still present in the
376
-
repository's history and still counted by this step, but the step
377
-
below now REJECTS any that a PR newly adds (#5471). It was never worth
520
+
repository's history and still counted by the counting step above,
521
+
but the step below now REJECTS any that a PR newly adds (#5471). It
522
+
was never worth
378
523
taking: it names no package, so its body reaches no CHANGELOG, and it
379
524
buys nothing the label does not. What it uniquely buys is risk --
380
525
unlike the label it is a REAL INPUT to changesets/action, and when
@@ -392,7 +537,7 @@ jobs:
392
537
echo "::error::This PR adds no changeset. If it releases nothing (including any 'skills/**' change -- see #5947), apply the 'skip-changeset' label; otherwise run 'pnpm changeset' and name the packages. An empty-frontmatter changeset is NOT a third option any more: the step below rejects newly added ones (#5471), because it is a real input to changesets/action and an all-empty set stalls the release silently and greenly (#4898). Full comparison in this step's log."
393
538
exit 1
394
539
fi
395
-
echo "This PR adds $ADDED changeset(s)."
540
+
echo "This PR adds $ADDED changeset(s), so it declares a release of its own."
396
541
397
542
# #5471: an empty-frontmatter changeset is rejected when this PR is the one
398
543
# introducing it. Ruled 2026-08-06 after the #5292 / PR #5467 prose route
@@ -422,9 +567,10 @@ jobs:
422
567
# place the red and green directions are pinned. It builds real temp git
423
568
# repositories and costs well under a second.
424
569
#
425
-
# Label handling: this step carries the same `steps.labels.outputs.skip`
426
-
# guard as every step above it, so it honours the LIVE label re-read
427
-
# (#5580 / #5625) and a rerun after labelling converges. That leaves one
570
+
# Label handling: this step carries the same live-label guard as the verdict
571
+
# step above it -- BOTH reads, the fast path and the settling one (#5580 /
572
+
# #5625 / #6378) -- so a label that lands inside the window exempts this
573
+
# step on the same run, and a rerun after labelling converges. That leaves one
428
574
# cell open and it is recorded rather than implied: a PR carrying BOTH the
429
575
# `skip-changeset` label AND a new empty changeset is not caught, because
430
576
# the whole job is exempt. Closing it would mean running this step outside
@@ -440,7 +586,9 @@ jobs:
440
586
# and reports it against an author who never touched the file. Same defect,
441
587
# opposite direction (a false RED here, a false GREEN up there), one base.
442
588
- name: Reject an empty-frontmatter changeset added by this PR
0 commit comments