From c0e796b6c9f96d115e03848b5efebdcf7b6500cc Mon Sep 17 00:00:00 2001 From: claude-bot-andresmgsl Date: Fri, 24 Jul 2026 12:50:09 +0000 Subject: [PATCH 1/3] =?UTF-8?q?fix:=20review=5Frequested=20wakes=20the=20s?= =?UTF-8?q?weep=20=E2=80=94=20blocker:unrequested=20clears=20when=20the=20?= =?UTF-8?q?ask=20lands?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The reconciler's rule was right and blind: the caller never listened on review_requested/review_request_removed, so the one event that falsifies (or restores) blocker:unrequested could not clear it, and a quiet repo wore the red flag until the advisory cron (#137's timeline: 93 seconds, cleared only by an unrelated PR's push). - self-labels.yml + the CONSUMERS.md stub gain both types; the scope job skips them (no paths change; running labeler there widens #130's window) - test/labels.test.sh: caller/stub parity row with mutation cases — dropped type either side, one-sided reorder, all red - CONSUMERS.md no longer claims trigger adoption is a bare pin bump; the pending stub edit is named and rides the first tag carrying ceremony#137 Co-Authored-By: Claude Fable 5 --- .github/workflows/labels.yml | 8 ++++++- .github/workflows/self-labels.yml | 6 ++++- changelog.d/137.md | 10 +++++++++ docs/CONSUMERS.md | 14 +++++++++--- test/labels.test.sh | 37 +++++++++++++++++++++++++++++++ 5 files changed, 70 insertions(+), 5 deletions(-) create mode 100644 changelog.d/137.md diff --git a/.github/workflows/labels.yml b/.github/workflows/labels.yml index 0cea584..034b74e 100644 --- a/.github/workflows/labels.yml +++ b/.github/workflows/labels.yml @@ -30,10 +30,16 @@ jobs: scope: # Not on labeled/unlabeled: those events change no paths, so labeler has # nothing new to derive — and label churn is precisely what they are. + # review_requested/review_request_removed likewise change no paths — they + # exist to wake reconcile (#137) — and running labeler on them widens + # exactly the window #130 documents, where a label written during a + # scope run is clobbered. if: >- github.event_name == 'pull_request_target' && github.event.action != 'labeled' && - github.event.action != 'unlabeled' + github.event.action != 'unlabeled' && + github.event.action != 'review_requested' && + github.event.action != 'review_request_removed' runs-on: ubuntu-latest concurrency: group: labels-scope-${{ github.event.pull_request.number }} diff --git a/.github/workflows/self-labels.yml b/.github/workflows/self-labels.yml index 53ba9ea..e9b8dac 100644 --- a/.github/workflows/self-labels.yml +++ b/.github/workflows/self-labels.yml @@ -10,7 +10,11 @@ on: issues: types: [opened, edited, assigned, unassigned, labeled, unlabeled, closed, reopened] pull_request_target: - types: [opened, reopened, ready_for_review, converted_to_draft, synchronize, labeled, unlabeled] + # review_requested/review_request_removed wake the sweep that clears (or + # restores) blocker:unrequested — without them the one event that makes + # the label false could not clear it, and a quiet repo wore the red flag + # until the advisory cron (#137). + types: [opened, reopened, ready_for_review, converted_to_draft, synchronize, labeled, unlabeled, review_requested, review_request_removed] permissions: contents: read checks: read # mergeability/check-rollup read for PR state diff --git a/changelog.d/137.md b/changelog.d/137.md new file mode 100644 index 0000000..f4980d4 --- /dev/null +++ b/changelog.d/137.md @@ -0,0 +1,10 @@ +### Fixed + +- `blocker:unrequested` now clears the moment the panel is asked: the labels + caller (and the `docs/CONSUMERS.md` stub) listens on `review_requested` and + `review_request_removed`, so the one event that falsifies the label — or + makes it true again — wakes the reconcile sweep instead of waiting for an + unrelated push or the advisory cron. The `scope` job skips both events: + they change no paths, and running the labeler on them widens the #130 + clobber window. Adopting the new triggers is a stub edit riding the pin + bump to the first tag carrying this change (#137). diff --git a/docs/CONSUMERS.md b/docs/CONSUMERS.md index 5ad6ee8..d8fe2c4 100644 --- a/docs/CONSUMERS.md +++ b/docs/CONSUMERS.md @@ -284,7 +284,10 @@ on: schedule: [{cron: "*/15 * * * *"}] # advisory; the handoff label is the real wake workflow_dispatch: # bootstraps missing labels on a fresh repo pull_request_target: - types: [opened, reopened, ready_for_review, converted_to_draft, synchronize, labeled, unlabeled] + # review_requested/review_request_removed are unreleased — not in 0.2.0; + # add them with the pin bump to the first tag carrying ceremony#137. They + # wake the sweep that clears blocker:unrequested when the panel is asked. + types: [opened, reopened, ready_for_review, converted_to_draft, synchronize, labeled, unlabeled, review_requested, review_request_removed] # Unreleased — not in 0.1.0; add only with the first tag carrying ceremony#32. issues: types: [opened, labeled, unlabeled, assigned, unassigned, closed] @@ -311,8 +314,13 @@ to the first tag carrying ceremony#32; never mix refs to adopt it early. `pull_request_target` is intentional: fork PRs need the base repository's token to write labels. The reusable workflow executes no PR code. It checks out only the consumer's base branch and the pinned ceremony implementation. -The #52 ruling invariants ride exactly these triggers — the caller above is -unchanged since #18, so adopting them is a pin bump, not a stub edit. +The #52 ruling invariants ride exactly these triggers — but the caller above +is no longer the #18 shape, so adopting current triggers is a stub edit, not +a bare pin bump. The pending edit is `review_requested` and +`review_request_removed` (#137): the wake that clears `blocker:unrequested` +the moment the panel is asked, without which a quiet repo wears that flag +until the advisory cron. Make that edit with the pin bump to the first tag +carrying ceremony#137 — never before it and never through mixed refs. `.github/labels.conf` has one mandatory panel setting, one mandatory `triage-actors` setting, and then zero or more scope rows: diff --git a/test/labels.test.sh b/test/labels.test.sh index 6d35a3a..e571ca2 100755 --- a/test/labels.test.sh +++ b/test/labels.test.sh @@ -65,4 +65,41 @@ check "PR author is recused from the required panel" 0 "one three" printf '%s\n' check "LABELS.md enumerates no repo's scope labels" 1 "0" \ grep -c 'scope:[a-z0-9]' "$ROOT/LABELS.md" +# The caller's pull_request_target list and the CONSUMERS.md stub's must be +# the same list. review_requested/review_request_removed are the wake that +# clears blocker:unrequested — the label sat false for as long as a quiet +# repo stayed quiet because the one event that falsifies it was never +# listed (#137). The stub is prose, so nothing but this row keeps the two +# lists from drifting: a type in one file only is a wake that fires at home +# and nowhere in the fleet, or the reverse. +pr_target_types() { # $1 = file → its pull_request_target types line, unindented + awk '/pull_request_target:/{f=1; next} f && /types: /{sub(/^ */,""); print; exit}' "$1" +} +types_in_sync() { # $1 = caller, $2 = stub → 0 when both lists exist and match + local a b + a="$(pr_target_types "$1")" b="$(pr_target_types "$2")" + [ -n "$a" ] && [ "$a" = "$b" ] +} +CALLER="$ROOT/.github/workflows/self-labels.yml" +STUB="$ROOT/docs/CONSUMERS.md" +check "caller and stub pull_request_target lists are identical" 0 "" \ + types_in_sync "$CALLER" "$STUB" +# shellcheck disable=SC2016 # expansion belongs to the nested bash +check "the caller lists both review-request wakes" 0 "" bash -c \ + 'awk "/pull_request_target:/{f=1; next} f && /types: /{print; exit}" "$1" | + grep -F review_requested | grep -qF review_request_removed' _ "$CALLER" +# the failing cases: drop a type from either file, or reorder one list only, +# and the identity row above goes red — exercised here on mutated copies +mut_caller="$TMP/mut-caller.yml" mut_stub="$TMP/mut-stub.md" +sed 's/, review_request_removed//' "$CALLER" >"$mut_caller" +check "a type dropped from the caller goes red" 1 "" \ + types_in_sync "$mut_caller" "$STUB" +sed 's/, review_request_removed//' "$STUB" >"$mut_stub" +check "a type dropped from the stub goes red" 1 "" \ + types_in_sync "$CALLER" "$mut_stub" +sed 's/review_requested, review_request_removed/review_request_removed, review_requested/' \ + "$STUB" >"$mut_stub" +check "a reorder in one list only goes red" 1 "" \ + types_in_sync "$CALLER" "$mut_stub" + summary From 48a3052934fd03f397982efa219bc2bcea15b9c0 Mon Sep 17 00:00:00 2001 From: claude-bot-andresmgsl Date: Fri, 24 Jul 2026 13:13:31 +0000 Subject: [PATCH 2/3] docs: CONSUMERS release-state notes say 0.2.0 shipped the issues: block (#137 D6) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The three sites called ceremony#32 machinery unreleased after 0.2.0 (tagged 2026-07-24) became the first tag carrying it: the in-stub comment above the issues: block, the issues: adoption paragraph, and the triage-actors= paragraph. All three now state availability at 0.2.0 and later; the 0.1.0-omission guidance and the parse-failure sentence stay — still true. No type list moves (the caller/stub issues: subset drift is #144, not this PR). Co-Authored-By: Claude Fable 5 --- docs/CONSUMERS.md | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/docs/CONSUMERS.md b/docs/CONSUMERS.md index d8fe2c4..92cec78 100644 --- a/docs/CONSUMERS.md +++ b/docs/CONSUMERS.md @@ -288,7 +288,8 @@ on: # add them with the pin bump to the first tag carrying ceremony#137. They # wake the sweep that clears blocker:unrequested when the panel is asked. types: [opened, reopened, ready_for_review, converted_to_draft, synchronize, labeled, unlabeled, review_requested, review_request_removed] - # Unreleased — not in 0.1.0; add only with the first tag carrying ceremony#32. + # Available at 0.2.0 and later (the first tag carrying ceremony#32); a + # consumer pinned to 0.1.0 omits this block. issues: types: [opened, labeled, unlabeled, assigned, unassigned, closed] permissions: @@ -307,9 +308,10 @@ repositories allow check data to be read regardless, but a private consumer needs both explicit reads above; without them the failure appears as an empty `state:*` axis on the board rather than a red workflow run. -The `issues:` trigger is **unreleased** and is not in `0.1.0`. A consumer -pinned to `0.1.0` omits it. Add it only when bumping every ceremony reference -to the first tag carrying ceremony#32; never mix refs to adopt it early. +The `issues:` trigger is available at `0.2.0` and later — `0.2.0` is the +first tag carrying ceremony#32. A consumer pinned to `0.1.0` omits it. Adopt +it only by bumping every ceremony reference to `0.2.0` or later; never mix +refs to adopt it early. `pull_request_target` is intentional: fork PRs need the base repository's token to write labels. The reusable workflow executes no PR code. It checks @@ -332,11 +334,11 @@ scope:cli|C5DEF5|The command-line surface scope:docs|C5DEF5|Documentation ``` -The mandatory `triage-actors=` setting is also **unreleased** and is not -accepted by `0.1.0`. At that tag the file contains `panel=` plus scope rows -only; adding `triage-actors=` is a parse failure, not an ignored setting. Add -it at the same pin bump as the `issues:` trigger, to the first tag carrying -ceremony#32 — never before it and never through mixed refs. +The mandatory `triage-actors=` setting is likewise accepted at `0.2.0` and +later, and not by `0.1.0`. At that tag the file contains `panel=` plus scope +rows only; adding `triage-actors=` is a parse failure, not an ignored setting. +Add it at the same pin bump as the `issues:` trigger — `0.2.0` or later — +never before it and never through mixed refs. Both actor lists are whitespace-separated. `triage-actors` names the identities allowed to mint issues without the sweep applying `needs-triage`. Label rows use exactly From 89de86c46091f34a6d92d1cfa8c10dce4c1e30aa Mon Sep 17 00:00:00 2001 From: claude-bot-andresmgsl Date: Fri, 24 Jul 2026 13:20:22 +0000 Subject: [PATCH 3/3] =?UTF-8?q?chore:=20retrigger=20checks=20=E2=80=94=20q?= =?UTF-8?q?ueued=20reconcile=20duplicate=20was=20cancelled=20(#139=20shape?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 48a3052's only labels/reconcile entry is a queue-evicted CANCELLED (its scope sibling in the same run passed); an all-cancelled group keeps blocking by design under #139's rule, so only a fresh run on this head can clear the manufactured red. No verdict binds 48a3052 yet, so this head move stales nothing. Co-Authored-By: Claude Fable 5