Skip to content

Commit c6b3eff

Browse files
mattmilleraiclaude
andauthored
fix(workflows): make workflows_ref required, guard empty refs, lint the default (BE-5546) (#103)
* fix(workflows): make workflows_ref required, guard empty refs, lint the default (BE-5546) cursor-review.yml, groom.yml and agents-md-integrity.yml load their backing scripts at run time from `workflows_ref`, which defaulted to the floating `main` branch. A caller that SHA-pinned `uses:` but omitted the input pinned the workflow while loading MUTABLE scripts into jobs holding write permissions — the pin proved nothing about the code that ran. - Drop `default: main`, set `required: true`, and rewrite the descriptions + header examples to say the input is required and must equal the `uses:` SHA. - Add a fail-fast guard immediately before every assets checkout: `::error` + exit 1 on an empty ref, `::warning` (non-fatal) when it is not a full 40-hex SHA. The guard is mandatory, not belt-and-braces: GitHub does NOT enforce `required: true` for workflow_call inputs, so an omitted input arrives as '' and `actions/checkout` with `ref: ''` silently takes the default branch. - Add .github/workflow-pins/ — a stdlib-only lint (plus tests and test-workflow-pins.yml) that fails if any `workflow_call` workflow declares a `default:` for `workflows_ref`, so the hole cannot come back. Every caller GitHub code search can see (42 files across org and external repos) already passes an explicit 40-hex ref, so no caller breaks and no bump-callers rollout is needed. * fix(workflows): close the review panel's gaps in the workflows_ref lint + guard (BE-5546) Addresses the cursor-review panel's findings on #103. Guard shell (all 12 copies): trim before the empty test — actions/checkout reads `ref` via core.getInput, which trims, so a whitespace-only value was an empty ref to IT while passing a bare -z test. Echo only the stripped form: dropping newlines also stops a multi-line value from satisfying the line-oriented grep on one 40-hex line among many, and from smuggling a ::workflow command:: into the log. Lint: the default was only half the hole, so the checker now also requires the empty-ref guard in the same job as every `ref: ${{ inputs.workflows_ref }}` checkout — a NEW job reopened the `ref: ''` fallback with a default-only lint still green, because there was never a default to find. Exempt workflows are not held to it (they still have a default, so '' cannot arrive). Parser: a trailing comment on `on:` no longer reads as an inline trigger list (it silently dropped the file from the lint); the flow-mapping form `workflows_ref: {type: string, default: main}` is caught; the default scan is scoped to the input's own property indent, so a wrapped `description: >-` line starting with `default:` cannot fail a compliant workflow; quoted keys no longer hide a declaration or a default; and a file that USES the input but whose declaration the parser cannot locate is now a hard error rather than a silent skip — "not applicable" and "I could not read this" must not look alike. KNOWN_EXEMPT staleness now also catches an entry whose workflow was renamed or deleted, which would otherwise pre-exempt whatever reused the filename. The list only applies to this repo's own workflows dir, where it means something. Tests: 13 -> 32, plus a second CLI smoke test that an unguarded checkout really exits non-zero. * docs(workflow-pins): correct the KNOWN_EXEMPT staleness comment (BE-5546) The comment still described the narrow original behaviour ("one whose workflow no longer has the default") after the check was widened in review to also catch an entry whose workflow was renamed or deleted — which is the case that would otherwise pre-exempt a future workflow reusing the filename. * fix(workflow-pins): catch the flow-mapping form of the ref checkout (BE-5546) Addresses CodeRabbit's findings on #103. The guard lint anchored on a `ref:` key at line start, so the one-line flow-mapping spelling of the same checkout — with: {repository: Comfy-Org/github-workflows, ref: "${{ inputs.workflows_ref }}"} — was invisible to it: `find_unguarded_ref_checkouts` reported nothing for a job that had no guard at all. `_CONSUMES_RE` had the same anchor, so a file whose only use was written that way also escaped the "the lint is NOT covering this file" hard error, turning the checker's loudest failure into silence. This is the same one-line bypass already barred for `default:`, wearing braces. Both patterns now have a flow-form sibling behind a small helper. The flow pattern stops the value at the entry boundary (`[^,}]`) so `{ref: v1, path: ${{ inputs.workflows_ref }}}` is not misread as a ref checkout — a greedy match there would fail a workflow that never checks out at the input. The guard detector is deliberately left block-only: a guard written in flow style reads as ABSENT, which fails loudly rather than passing an unverified checkout. Verified against the real tree: still the same 4 workflows checked, the same 12 guarded sites, no new hits. 32 -> 36 tests. Also from the same review round: the cursor-review README's knob table still said "All optional, with defaults" after workflows_ref became required with none; the pins test workflow had no concurrency group, so every push to a PR left the superseded lint run burning a runner; and CheckDirTests leaked twelve temp directories per run. * fix(workflow-pins): catch the multi-line spelling of the ref checkout (BE-5546) Self-review of the previous commit, which closed the one-line flow-mapping bypass in the guard detector but left its vertical sibling open. Both ref patterns required `inputs.workflows_ref` to appear on the SAME line as the `ref:` key. YAML does not require that. All four of these are one working checkout to Actions, and every one of them reported an unguarded job as clean: ref: >- ref: | ref: ref: " ${{ … }} ${{ … }} ${{ … }} ${{ … }}" So the lint's whole subject — a job that checks out at the input without the fail-fast guard — was invisible whenever the value was carried on the next line. `_CONSUMES_*` had the same blind spot for the block-scalar form, so a file whose only use was written that way also escaped the "the lint is NOT covering this file" hard error: the loudest failure the checker has, silenced again, this time by a line break rather than a pair of braces. A `ref:` key whose value is empty, a block-scalar indicator (`|`/`>` with any chomping or explicit-indent modifier), or an opening quote now opens a window over the more-indented lines that follow. The window is not itself a finding — a hit needs the input to actually appear in the continuation — so `ref: >-` over a literal SHA stays clean, and the window closes at the first line back at or above the key's indent so a later sibling key is never blamed on the `ref:`. Verified by probe, not by inspection: all four shapes now flag, the flow and block forms still flag, and the four false-positive shapes stay clean. Real tree unchanged — same 4 workflows checked, same 12 guarded sites, no new hits — and the check is still non-vacuous: stripping the guards flags all 12. 44 tests (was 36). actionlint clean; cursor-review 40, agents-md-integrity 18, groom 170, bump-callers 123 green; AGENTS.md still 150 lines. * fix(workflow-pins): catch the commented spelling of the ref checkout (BE-5546) A `#` comment between a mapping key and a value on the next line was unhandled, which reopened the same bypass the previous two commits closed: ref: # the pinned ref ${{ inputs.workflows_ref }} Valid YAML, and the value is exactly the input — a working, unguarded checkout. But the comment left the key line looking like a finished scalar, so `_REF_KEY_OPEN_RE` never opened the continuation window and the job read as having no ref checkout at all. Worse, `_CONSUMES_BLOCK_RE` missed it too, so the "this file uses the input but the lint can't parse its declaration" backstop stayed silent as well: an uncovered workflow passed clean instead of failing loudly. Same for a comment after a block header (`ref: | # …`), which YAML also allows. Teach the four affected patterns that a comment may sit between a key and its value — but not after an opening QUOTE, where `#` is string content. Also let a trailing comment ride on the guard's `WORKFLOWS_REF:` env line. That one was a false FAILURE rather than a bypass: the guard is doing its job, so refusing to see it fails a compliant workflow. This is the opposite trade-off from the flow-form guard, which stays unrecognized on purpose. Six tests, covering both directions — the commented checkout is caught with and without a guard, and a commented LITERAL ref (plus a commented-out `ref:` line, and one whose input feeds a later key) still is not read as a use. * fix(workflow-pins): the env binding is not the guard — verify the rejection (BE-5546) The guard detector keyed on the step's `env:` binding alone (`WORKFLOWS_REF: ${{ inputs.workflows_ref }}`), on the reasoning that nothing else uses that name. But the binding is only how the guard RECEIVES the ref, not the guard: any step that merely handles the value — one that echoes it, or clones with it — carries the same binding, and it marked the whole job guarded, so every later checkout passed unexamined. Reproduced end to end before touching anything: a step whose entire body is `echo "$WORKFLOWS_REF"`, followed by a plainly unguarded `ref: ${{ inputs.workflows_ref }}`, returned zero errors from `check_dir`. The lint's own subject, failing silently. A step now counts as the guard only if it is also seen to REJECT the empty value — an emptiness test and a non-zero exit, both inside that same step. Same probe found the second half: a checkout does not have to name the input. Hoist it to a job-level `env:` — the natural refactor once several steps want it — and `ref: ${{ env.WORKFLOWS_REF }}` read as no ref use at all, dropping the checkouts from coverage AND (before this fix) marking the job guarded on the way past. Names bound to the input inside `env:` blocks are collected first, and a `ref:` reaching one counts as a use. Scoped to `env:` blocks on purpose: the checkout's own `ref:` is a binding too, and collecting it would make `env.ref`/`$ref` anywhere read as the input. Both stay fail-closed: a step the parser cannot resolve is not a guard, so the checkout it precedes is reported rather than passed unverified. Verified non-vacuously rather than by a green run: on the real tree, 0 unguarded across the 12 sites; stripping the env bindings flags all 12; and — the case this commit adds — leaving the bindings in place while neutering only the `-z` test also flags all 12, which the old detector passed. Suite 50 → 57. * fix(workflow-pins): tie the guard's empty test to the ref and its own branch (BE-5546) CodeRabbit caught the near-match left by the previous commit, and it was real — reproduced before touching anything. Requiring "an emptiness test somewhere in the step, a non-zero exit somewhere in the step" let the two halves be about entirely different things: if [ -z "$UNRELATED" ]; then echo "empty"; fi if [ "$UNRELATED" = "blocked" ]; then exit 1; fi ...marked the job guarded. That is a likelier accident than the bare decoy the previous commit fixed: arg validation sitting next to a step that clones at the ref is ordinary shell, not a contrivance. The `-z` must now name the ref — `WORKFLOWS_REF`, or a variable derived from it, since the real guard tests `$REF` assigned through a `printf | tr` strip — and the non-zero exit must sit in THAT test's own branch, ending at the matching `fi`/`else`. The one-liner `[ -z "$REF" ] && exit 1` counts on its own line. Verified by mutation rather than by a green run, since a green run is what the bug produced. Against the real tree, 0 unguarded across the 12 sites, and each of five separate mutations flags all 12: dropping the env binding, neutering the `-z` test, testing the WRONG variable, moving the exit out of the empty branch, and breaking the derivation hop so `REF` no longer carries the value. Suite 57 → 61. * fix(workflow-pins): the guard's condition must be the empty test, not contain it (BE-5546) Second CodeRabbit finding on the same function, and real again — reproduced before changing anything. Tying the `-z` to the ref and the exit to that test's branch still accepted a condition that merely CONTAINS the emptiness test: if [ -z "$REF" ] && [ "$OTHER" = "blocked" ]; then exit 1 fi That marks the job guarded, but it does not fail for every empty ref — empty `REF` with `OTHER` unset falls straight through to the checkout. The emptiness test must now BE the whole condition. A text-level lint cannot evaluate shell, so every compound is rejected as ambiguous — that includes a widening `||`, which is safe in fact but not worth a special case in a detector whose entire bias is fail-closed. Both canonical forms still pass, including the single-line `if …; then exit 1; fi` and `[ -z "$REF" ] && exit 1` (a `run:` on the key's own line is YAML, not part of the condition — that cost a fix of its own). Six mutations now, each flagging all 12 sites while the real tree stays at 0: no binding, no `-z`, wrong variable, exit outside the branch, dead derivation hop, and the ANDed condition this commit adds. Suite 61 → 63. * fix(workflow-pins): the inline branch ends at `fi`, and its exit must be bare (BE-5546) Third CodeRabbit finding on this function, and right again — reproduced first. The single-line path searched everything after `then` for an exit, including past the branch: run: if [ -z "$REF" ]; then echo "missing ref"; fi; exit 1 The branch does not exit, so an empty ref walks on to the checkout. The multiline path had stopped at `fi` since it was written; the inline path had quietly stopped agreeing with it. Reviewing the fix for its own siblings — the failure mode on this PR has been that each fix leaves a weaker copy of the same assumption one layer down — found two more of the same shape, both accepted before this commit: if [ -z "$REF" ]; then [ "$X" = y ] && exit 1; fi # conditional exit [ -z "$REF" ] && echo warn; [ "$X" = y ] && exit 1 # && reaches the echo The multiline path never accepted these: it anchors `exit` at the start of its line, so a conditional exit is not one. Both inline paths now hold to that same rule — the branch ends at `fi`, and the exit must be a bare `exit N` command, the one the `&&` actually reaches. Three decoys rejected, both canonical forms still accepted. Eight mutations of the real workflows now, each flagging all 12 sites with the real tree at 0. Suite 63 → 66. * fix(workflow-pins): the branch's exit must be at its own nesting depth (BE-5546) Fourth CodeRabbit finding, and the exact mirror of the previous commit — which is what makes it worth stating rather than fixing quietly. That commit hardened the INLINE paths to require a bare, directly reachable `exit`, and I checked its siblings for the same drift. I checked the wrong siblings: the multiline path stops at the first `fi`/`else`/`elif`, so an `exit` nested inside an inner `if` was still read as the branch's own. if [ -z "$REF" ]; then if [ "$X" = y ]; then exit 1 fi fi Branch entered on an empty ref, exit conditional on something else, job reads as guarded. Reproduced before changing anything. The scan now tracks nesting and accepts `exit` only at the branch's own depth (`\bif\b` does not match inside `elif`, and a nested one-liner opens and closes on the same line). A test pins the other direction too: an exit AFTER a nested block is still the branch's own and still counts. Nine mutations of the real workflows now, each flagging all 12 sites with the real tree at 0. Suite 66 → 68. * fix(workflow-pins): a guard that can be skipped or soft-failed is not a guard (BE-5546) Found by sweeping the detector systematically rather than waiting for the next review round — four consecutive findings had all been one more spelling of shell, so I went looking somewhere else. Both of these are Actions-level, and every shell check in `is_guard_step` passes them happily because the shell is impeccable: - name: Require a pinned workflows_ref continue-on-error: true # `exit 1` no longer fails the job ... - name: Require a pinned workflows_ref if: github.event_name == 'push' # skipped outright on other events In both cases the checkout runs with an empty ref while the job reads as guarded. `continue-on-error: true` is the more complete bypass of the two: the guard runs, fails exactly as designed, and is ignored. Neither is evaluable in a text lint, so both disqualify the step rather than being assumed benign. `continue-on-error: false` is still a guard; the keys are matched only at the step's own indent, so a shell `if` in the run body is untouched. Eleven mutations of the real workflows now, each flagging all 12 sites with the real tree at 0. Suite 68 → 71. * fix(pr-size): require a pinned workflows_ref, guard empty refs, drain the exemption (BE-5858) (#124) pr-size.yml was the last workflow carrying a `workflows_ref` default, so a consumer could SHA-pin `uses: .../pr-size.yml@<sha>` and still build the check-pr-size tool from a floating `main` — the pin proving nothing about the code that actually ran. Apply the BE-5546 playbook to it: drop the default, mark the input required, and add the `Require a pinned workflows_ref` step (copied verbatim from cursor-review.yml) ahead of the tool checkout, since GitHub does not enforce `required: true` for workflow_call inputs and an omitted input arrives as '' that checkout resolves to the default branch. Only the `pr-size` job consumes the ref; the `comment` job checks out nothing, so it gets no guard. With the default gone, the KNOWN_EXEMPT entry would itself fail the lint (it hard-fails on a stale exemption by design), so it is removed — leaving the frozenset empty and every reusable workflow here held to both checks. The caller fleet was audited under BE-5856 and re-verified here: the roster has one entry, its caller already passes `workflows_ref` SHA-matched to its `uses:` pin, the check is not a required status on that repo's default branch, and a global code search finds no unenrolled caller. So dropping the default breaks nobody, and the shared bumper moves `uses:` and `workflows_ref` in lockstep. * docs(workflow-pins): document the job_workflow_sha exemption and length-guard shape The README still described the pre-merge state (groom.yml required+guarded like the others, no mention of pr-risk.yml's guard shape). Update it to match what the lint now actually checks. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * fix: resolve review feedback Merging main pulled in an unguarded workflows_ref checkout: main independently added the diff-size job's "Load check-pr-size tool" step (checking out ref: ${{ inputs.workflows_ref }}) after this branch forked, with no empty-ref guard ahead of it, since that guard convention is this PR's own contribution. Add the guard step the same way every other ref checkout in this workflow gets it, and update the self-test's expected guarded-site count (15 -> 16) to match. * fix: resolve review feedback is_guard_step treated an empty inline continue-on-error value (comment- only or bare "continue-on-error:") the same as false, so a step whose real value continued on the next, more-indented line - "continue-on-error: # note\n true" - passed as a guard. Actions reads the continuation as the value, so the guard's exit 1 would run exactly as designed and be ignored, letting the job carry on to the checkout anyway. Read the continuation line when the inline value is empty, and add a same-line-true / continuation-false regression test. --------- Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
1 parent 255187f commit c6b3eff

11 files changed

Lines changed: 2262 additions & 38 deletions

File tree

‎.github/cursor-review/README.md‎

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ jobs:
118118
diff_excludes: >-
119119
:!**/package-lock.json
120120
:!**/*.generated.*
121-
# Pin the prompts/scripts to the same ref you pin `uses:` to.
121+
# REQUIRED — the same SHA as the `uses:` pin above.
122122
workflows_ref: <sha>
123123
secrets:
124124
CURSOR_API_KEY: ${{ secrets.CURSOR_API_KEY }}
@@ -173,8 +173,9 @@ cases a gate is meant to catch.
173173

174174
## Configuration knobs
175175

176-
All optional, with defaults — pass them under `with:` in the caller. Full
177-
descriptions live in the [workflow header](../workflows/cursor-review.yml).
176+
All optional except `workflows_ref` (required, no default) — pass them under
177+
`with:` in the caller. Full descriptions live in the
178+
[workflow header](../workflows/cursor-review.yml).
178179

179180
| Input | Default | What it does |
180181
|---|---|---|
@@ -185,7 +186,7 @@ descriptions live in the [workflow header](../workflows/cursor-review.yml).
185186
| `extra_generated_globs` | `node_modules`, `dist`, `vendor`, minified/`.generated.` files | Extra globs the shared `check-pr-size` classifier treats as generated — excluded from BOTH the size count and the reviewed diff. |
186187
| `extra_lockfiles` | `''` | Extra lockfile base names for the classifier, on top of its built-ins. |
187188
| `diff_excludes` | `''` | Pathspecs excluded from the reviewed diff ONLY (not the size count) — back-compat escape hatch; prefer `extra_generated_globs`. |
188-
| `workflows_ref` | `main` | Ref this directory's prompts/scripts are loaded from. Pin to your `uses:` SHA. |
189+
| `workflows_ref` | **required** (no default) | Ref this directory's prompts/scripts are loaded from. Must be the same commit SHA as your `uses:` pin — omit it and the run fails fast, because pinning `uses:` while loading scripts from a mutable branch defeats the pin. |
189190
| `bot_app_id` | `''` | Optional GitHub App ID; when set (with `BOT_APP_PRIVATE_KEY`), the review posts under that App's identity instead of `github-actions[bot]`. |
190191
| `run_without_label` | `false` | Run on plain PR events instead of requiring the trigger label. Also requires widening the caller's `types:` — see [the setup guide](../../docs/callers/cursor-review.md). |
191192

‎.github/workflow-pins/README.md‎

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
# workflow-pins
2+
3+
An **internal repo lint** — unlike the other directories here, nothing in this
4+
one is loaded by a reusable workflow at run time. It guards a property of this
5+
repo's own workflow files.
6+
7+
- **`check_workflow_pins.py`** — for every `on: workflow_call` workflow in
8+
`.github/workflows/`, fails if it (1) declares a `default:` for its
9+
`workflows_ref` input, or (2) checks out at `ref: ${{ inputs.workflows_ref }}`
10+
in a job that does not run the empty-ref guard first — recognizing the
11+
canonical `-z` guard and the length/charset shape `pr-risk.yml` uses, and
12+
exempting the `github.job_workflow_sha` self-pin `groom.yml` uses instead of
13+
a guard (see below). Text-level parsing (this repo is stdlib-only — no
14+
PyYAML), the same constraint `bump-callers.sh` works under.
15+
- **`tests/`** — `unittest` suite, run by
16+
[`test-workflow-pins.yml`](../workflows/test-workflow-pins.yml) along with a
17+
CLI smoke test that a reintroduced default really exits non-zero.
18+
19+
```bash
20+
python3 .github/workflow-pins/check_workflow_pins.py
21+
```
22+
23+
## Why (BE-5546)
24+
25+
Every reusable workflow that loads its backing scripts at run time takes a
26+
`workflows_ref` input and checks this repo out at that ref. If that input
27+
defaults to a floating branch, a caller can SHA-pin `uses:` and *still* load
28+
**mutable** scripts — into jobs that hold write permissions. The pin then
29+
proves nothing about the code that actually runs.
30+
31+
So `cursor-review.yml`, `agents-md-integrity.yml`, `pr-size.yml`, and
32+
`refresh-reviewers.yml` declare `workflows_ref` with `required: true` and **no
33+
default**, and each job that consumes it runs a fail-fast guard before its
34+
assets checkout. The guard is not belt-and-braces: **GitHub does not enforce
35+
`required: true` for `workflow_call` inputs.** An omitted input arrives as
36+
`''`, and `actions/checkout` with `ref: ''` silently checks out the default
37+
branch — recreating the hole exactly. The guard also emits a (non-fatal)
38+
`::warning::` when the ref is not a full 40-hex SHA, since branch and tag refs
39+
can move between jobs mid-run. `pr-risk.yml` writes the same guarantee as one
40+
length/charset test (`${#WORKFLOWS_REF} -ne 40`, OR'd with a charset check)
41+
instead of a separate `-z` test — the lint recognizes both shapes: a length
42+
check alone already rejects an empty ref (length 0), and `||` only ever widens
43+
what a condition rejects, so one qualifying branch is enough regardless of
44+
what it is OR'd with.
45+
46+
`groom.yml` is the one workflow that skips the guard entirely (BE-4169): its
47+
`workflows_ref` defaults to `''`, and every checkout falls back to
48+
`${{ inputs.workflows_ref || github.job_workflow_sha }}` — the exact commit
49+
THIS reusable workflow was itself resolved from via the caller's `uses:` pin.
50+
That value can never be empty or mutable, so an omitted input self-pins
51+
instead of reaching a floating branch — the same guarantee the guard buys,
52+
bought without needing one. The lint recognizes this LITERAL fallback
53+
expression only; anything else OR'd in (a branch, a tag, another input) is the
54+
same hole wearing a different hat and stays covered by both checks.
55+
56+
The guard is copied inline into each consuming job rather than factored into a
57+
composite action **on purpose**: a composite would have to be loaded with
58+
`uses: Comfy-Org/github-workflows/.github/actions/…@<ref>` — the very ref being
59+
validated — and a job cannot `uses: ./…` before its checkout. Many copies of a
60+
16-line guard is the cost of not making the check depend on the thing it checks.
61+
62+
Deleting a `default:` is a one-line edit to undo, hence the lint. It covers
63+
**every** `workflow_call` workflow, not an allow-list of today's three, so a
64+
workflow added later is guarded the day it lands.
65+
66+
The lint checks the guard as well as the default, because the default is only
67+
half the hole. A **new job** — or a whole new reusable workflow — that checks
68+
out at `ref: ${{ inputs.workflows_ref }}` without the guard reopens the `ref: ''`
69+
default-branch fallback, and a default-only lint stays green throughout: there
70+
was never a `default:` to find. So every such checkout must be preceded, *in its
71+
own job*, by the guard step (a guard in job A does nothing for job B). An
72+
exempt workflow is not held to this — it still has its default, so an omitted
73+
input can never arrive as `''` — which puts it back under the check the moment
74+
its own ticket drops the default.
75+
76+
Two shapes the text parser is deliberately strict about: a `default` inside a
77+
flow mapping (`workflows_ref: {type: string, default: main}`) is caught even
78+
though it has no child lines to walk, and a file that *uses* `inputs.workflows_ref`
79+
but whose declaration the parser cannot locate is a hard **error**, not a quiet
80+
skip — "not applicable" and "I could not read this" must never look the same,
81+
or a shape the parser trips on drops out of coverage with CI still green.
82+
83+
The ref-checkout detector recognises all three YAML spellings of the same
84+
checkout — block (`ref: ${{ … }}`), flow (`with: {…, ref: "${{ … }}"}`), and a
85+
value carried on the following line (`ref: >-`, `ref: |`, or a bare `ref:`).
86+
They are one checkout to Actions, so a detector that knows only one of them
87+
reports an unguarded job as clean. Do not "simplify" the extra patterns away.
88+
The guard's own signature is matched in block form **only**, deliberately: a
89+
guard written in flow style reads as ABSENT and fails loudly, which is the right
90+
bias for a check whose whole job is noticing an absence.
91+
92+
`KNOWN_EXEMPT` in the script carries workflows with the same debt that are
93+
tracked under their own ticket. It is **empty today** — `pr-size.yml`, its last
94+
entry, was fixed in BE-5858 once its caller fleet had been audited, so no
95+
reusable workflow here is carved out of either check. The lint fails on a
96+
**stale** entry so the list drains itself rather than rotting — whether the
97+
workflow dropped its default (fixed) or no longer exists under that name at
98+
all (renamed or deleted), the latter being
99+
the case that would otherwise silently pre-exempt whatever later reuses the
100+
filename. The list is only applied to this repo's own `.github/workflows`: run
101+
against an ad-hoc `--workflows-dir` every entry would look stale.

0 commit comments

Comments
 (0)