From 8841d9711f2a45bd937d26e2cedc368c975990f1 Mon Sep 17 00:00:00 2001 From: dan-claude-bot Date: Sat, 1 Aug 2026 16:12:17 +0000 Subject: [PATCH] fix: checks_state never grades the label machine's own runs (#208) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The shared reconcile concurrency group displaces queued sweeps as CANCELLED, and the displaced run's successor attaches to a different PR — so on the victim the newest self entry stayed CANCELLED, scored FAILURE, and the sweep set blocker:ci-red off its own corpse every cadence (crew#227). Drop rollup entries whose workflowName matches SELF_WORKFLOW (defaulting to the ambient GITHUB_WORKFLOW — the caller's name, so no workflow edit and no hardcoded consumer name) before the newest-per-context collapse; an empty name filters nothing. A self-only rollup now honestly scores NONE, and a genuine foreign failure still blocks beside a cancelled self entry — the must-fail guard against re-opening #136. Co-Authored-By: Claude Fable 5 --- actions/labels-reconcile/labels-reconcile.sh | 35 ++++++++++++++++- changelog.d/208.md | 11 ++++++ test/labels-reconcile.test.sh | 40 ++++++++++++++++++++ 3 files changed, 85 insertions(+), 1 deletion(-) create mode 100644 changelog.d/208.md diff --git a/actions/labels-reconcile/labels-reconcile.sh b/actions/labels-reconcile/labels-reconcile.sh index 9e7d337..86ca3ab 100755 --- a/actions/labels-reconcile/labels-reconcile.sh +++ b/actions/labels-reconcile/labels-reconcile.sh @@ -49,6 +49,15 @@ LABELS="" # retirement heals the board instead of stranding a label nothing recomputes. RETIRED=(state:needs-rebase) STALE_AFTER=$((48 * 3600)) +# The workflow whose runs checks_state must never grade — its own (#208). +# GITHUB_WORKFLOW is ambient in every Actions step and names the CALLER (the +# consumer's PR-facing workflow, since consumers name the caller), so this +# self-serves with no workflow-file change. The explicit override exists for +# two readers: the fixtures, and #209's detached sweep caller, which will +# need to point this at the PR-facing caller's name once reconcile no longer +# runs inside it. Empty means "filter nothing" — a caller outside Actions +# (a local rehearsal, an older pin) must not silently start dropping entries. +SELF_WORKFLOW="${SELF_WORKFLOW:-${GITHUB_WORKFLOW:-}}" # The needs-ruling invariants (#52) — one implementation for both surfaces. # shellcheck source=lib/ruling.sh @@ -221,7 +230,27 @@ checks_state() { # rollup JSON on stdin → SUCCESS | FAILURE | PENDING | NONE | # a context whose entries are ALL cancelled never reported at all (a killed # or timed-out required job), so it keeps CANCELLED and still blocks — # discard needs a surviving verdict, never an empty context. - jq -r ' + # + # And one exclusion that comes before every rule above: the label machine + # never grades its own runs (#208). Every reconcile sweep serializes + # through one shared concurrency group, and GitHub records a displaced + # queued run as CANCELLED — there is no "superseded" conclusion for queue + # displacement. When the displaced run was born from a pull_request_target + # event, that cancelled entry attaches to the victim PR while its + # SUCCESSOR — triggered by a different PR or an issues event — attaches + # elsewhere, so the #139 carve-out's premise (a surviving sibling on the + # same PR) fails structurally: on the victim the newest self entry stays + # CANCELLED, the deny-list scores it FAILURE, and the sweep sets + # blocker:ci-red off its own corpse — then re-affirms it every cadence. + # Proven on crew#227: every real check green, the only red rollup entry + # the sweep's own displaced run. So drop every entry belonging to + # $SELF_WORKFLOW before the newest-per-context collapse. Accepted + # consequences: a rollup of ONLY self entries scores NONE (honestly: no + # checks — never SUCCESS), and a genuine reconcile failure surfaces on the + # Actions tab instead of as blocker:ci-red, which is right because no PR + # edit can fix the label machinery. An empty $self filters nothing — the + # exclusion must never widen into dropping entries on a guess. + jq -r --arg self "$SELF_WORKFLOW" ' if (has("statusCheckRollup") | not) then "UNREADABLE" else # NEUTRAL and SKIPPED satisfy branch protection — a skipped required check @@ -262,7 +291,11 @@ checks_state() { # rollup JSON on stdin → SUCCESS | FAILURE | PENDING | NONE | # and treating it as newest keeps an undateable in-flight run from being # discarded in favour of a stale success. Every ambiguity resolves toward # "not settled". + # The #208 exclusion (header above): self entries leave the rollup here, + # BEFORE the group_by — a self-only context must vanish entirely, never + # survive as an all-cancelled context that still classifies FAILURE. | [ (.statusCheckRollup // [])[] + | select($self == "" or (.workflowName // "") != $self) | { ctx: [.workflowName // "", .name // .context // ""], at: ([.startedAt, .createdAt, .completedAt] | map(select(type == "string" and . != "" diff --git a/changelog.d/208.md b/changelog.d/208.md new file mode 100644 index 0000000..aa1d9b7 --- /dev/null +++ b/changelog.d/208.md @@ -0,0 +1,11 @@ +### Fixed + +- `checks_state` drops rollup entries belonging to the workflow it runs + inside — `SELF_WORKFLOW`, defaulting to the ambient `GITHUB_WORKFLOW` — + before the newest-per-context collapse: the label machine never grades + its own runs, and an empty name filters nothing (#208). +- A sweep displaced from the shared concurrency queue attaches CANCELLED to + its PR while its successor attaches elsewhere, so the sweep set + `blocker:ci-red` off its own displaced run and re-affirmed it every + cadence (crew#227). A rollup of only self entries now honestly scores + NONE (#208). diff --git a/test/labels-reconcile.test.sh b/test/labels-reconcile.test.sh index 00fcafd..ff42d69 100755 --- a/test/labels-reconcile.test.sh +++ b/test/labels-reconcile.test.sh @@ -351,6 +351,13 @@ run_() { jq -n --arg n "$1" --arg o "$2" --arg t "${3:-2026-07-20T15:00:00Z}" \ ctx_() { jq -n --arg n "$1" --arg s "$2" --arg t "${3:-2026-07-20T15:00:00Z}" \ '{__typename:"StatusContext", context:$n, state:$s, createdAt:$t}'; } +# Pinned empty for every fixture below except the #208 block, which sets its +# own. The script defaults SELF_WORKFLOW from the ambient GITHUB_WORKFLOW — +# present in any CI run of this suite — and an inherited name that happened +# to match a fixture's workflowName ("ci", "labels") would silently drop +# entries these fixtures rely on. The verdicts must not flip with the runner. +SELF_WORKFLOW="" + expect "no checks at all is NONE" NONE "$(rollup '[]' | checks_state)" # A failed fetch leaves no rollup KEY; a PR with no checks leaves an empty # ARRAY. Collapsing the two let an API hiccup read as "nothing is failing" — @@ -429,6 +436,39 @@ expect "a cancelled newest over an earlier FAILURE is still that failure" FAILUR "$(rollup "[$(rec_ FAILURE 2026-07-24T12:16:17Z 2026-07-24T12:17:06Z),\ $(rec_ CANCELLED 2026-07-24T12:16:41Z 2026-07-24T12:16:41Z)]" | checks_state)" +# -- the #208 exclusion: the label machine never grades its own runs. The +# shared concurrency group displaces queued sweeps as CANCELLED, and the +# displaced run's successor was triggered by a DIFFERENT PR or an issues +# event — so on the victim PR the #139 carve-out's premise (a surviving +# sibling on the same head) fails structurally: the newest self entry +# stays CANCELLED, and the sweep set blocker:ci-red off its own corpse, +# re-affirming it every cadence. Proven on crew#227: every real check +# green, the only red rollup entry the sweep's own displaced run. rec_ +# already builds entries under workflowName "labels"; naming that as +# self must drop them whole, before the newest-per-context collapse. +SELF_WORKFLOW="labels" +expect "a displaced self CANCELLED beside green others is no verdict (crew#227)" SUCCESS \ + "$(rollup "[$(run_ a SUCCESS),$(run_ b SUCCESS),\ + $(rec_ CANCELLED 2026-08-01T15:17:56Z 2026-08-01T15:17:59Z)]" | checks_state)" +expect "a FAILED self run surfaces on the Actions tab, not as the PR's red" SUCCESS \ + "$(rollup "[$(run_ a SUCCESS),$(run_ b SUCCESS),\ + $(rec_ FAILURE 2026-08-01T15:00:00Z 2026-08-01T15:01:00Z)]" | checks_state)" +expect "a rollup of ONLY self entries is honestly NONE, never SUCCESS" NONE \ + "$(rollup "[$(rec_ CANCELLED 2026-08-01T15:17:56Z 2026-08-01T15:17:59Z)]" | checks_state)" +# must-fail: the filter keys on the self workflow ALONE. Widening it — any +# cancelled entry, any labels-shaped name — certifies a genuine foreign +# failure green, which is #136's unknown-as-green shape all over again. +expect "a genuine foreign FAILURE still blocks beside a cancelled self entry" FAILURE \ + "$(rollup "[$(run_ a FAILURE),\ + $(rec_ CANCELLED 2026-08-01T15:17:56Z 2026-08-01T15:17:59Z)]" | checks_state)" +# ...and an empty self filters NOTHING: outside Actions no workflow name is +# ambient, and the exclusion must never drop entries on a guess — the same +# displaced-self rollup keeps blocking there, all-cancelled context intact. +SELF_WORKFLOW="" +expect "an empty SELF_WORKFLOW filters nothing — the same rollup still blocks" FAILURE \ + "$(rollup "[$(run_ a SUCCESS),$(run_ b SUCCESS),\ + $(rec_ CANCELLED 2026-08-01T15:17:56Z 2026-08-01T15:17:59Z)]" | checks_state)" + # -- a run still IN FLIGHT. `run_()` cannot express this: it always carries a # real completedAt, which is exactly why the supersede rule shipped dating # runs by completion and nothing caught it. Both spellings of "no