mirror of
https://github.com/Comfy-Org/github-workflows.git
synced 2026-11-05 00:17:37 +00:00
[PR #103] [MERGED] fix(workflows): make workflows_ref required, guard empty refs, lint the default (BE-5546) #113
Labels
No labels
cursor-review-catalog-drift
groom
groom
pull-request
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
Comfy-Org/github-workflows#113
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
📋 Pull Request Information
Original PR: https://github.com/Comfy-Org/github-workflows/pull/103
Author: @mattmillerai
Created: 8/1/2026
Status: ✅ Merged
Merged: 8/12/2026
Merged by: @mattmillerai
Base:
main← Head:matt/be-5546-require-workflows-ref📝 Commits (10+)
b668437fix(workflows): make workflows_ref required, guard empty refs, lint the default (BE-5546)a924144fix(workflows): close the review panel's gaps in the workflows_ref lint + guard (BE-5546)697e07edocs(workflow-pins): correct the KNOWN_EXEMPT staleness comment (BE-5546)a8f101efix(workflow-pins): catch the flow-mapping form of the ref checkout (BE-5546)c9c4cf7fix(workflow-pins): catch the multi-line spelling of the ref checkout (BE-5546)c59ceaefix(workflow-pins): catch the commented spelling of the ref checkout (BE-5546)66984aefix(workflow-pins): the env binding is not the guard — verify the rejection (BE-5546)2866ac1fix(workflow-pins): tie the guard's empty test to the ref and its own branch (BE-5546)ad1036efix(workflow-pins): the guard's condition must be the empty test, not contain it (BE-5546)69e8a4efix(workflow-pins): the inline branch ends atfi, and its exit must be bare (BE-5546)📊 Changes
11 files changed (+2262 additions, -38 deletions)
View changed files
📝
.github/cursor-review/README.md(+5 -4)➕
.github/workflow-pins/README.md(+101 -0)➕
.github/workflow-pins/check_workflow_pins.py(+815 -0)➕
.github/workflow-pins/tests/test_check_workflow_pins.py(+937 -0)📝
.github/workflows/agents-md-integrity.yml(+36 -9)📝
.github/workflows/cursor-review.yml(+173 -9)📝
.github/workflows/pr-size.yml(+33 -8)📝
.github/workflows/refresh-reviewers.yml(+24 -0)➕
.github/workflows/test-workflow-pins.yml(+108 -0)📝
AGENTS.md(+16 -3)📝
README.md(+14 -5)📄 Description
ELI-5
Our shared workflows keep their real logic (prompts, checkers, the groom briefs) here, and each caller repo says "run workflow X, and load its scripts from ref Y". Ref Y is the
workflows_refinput, and it used to default tomain. So a repo could carefully pin the workflow to an exact commit and still be loading the scripts from whatever landed onmainfive minutes ago — into jobs that hold write permissions. The careful pin bought nothing.This PR removes that default on the three affected workflows, makes the input required, fails the run fast when it is missing, and adds a lint so the default can't quietly come back.
What changed
workflows_refis required, with no default — incursor-review.yml,groom.yml,agents-md-integrity.yml,pr-size.yml(added mid-review, judgment call below), andrefresh-reviewers.yml(main's own design already required it; this PR only closed a guard gap main introduced there — see judgment calls). Descriptions and header examples now say it is required and must equal theuses:SHA.pr-size.yml, and mergingmainpicked upcursor-review.yml's newpreflight/ledger/diff-sizejobs andrefresh-reviewers.yml).::error+exit 1on an empty value; a non-fatal::warningwhen the value isn't a full 40-hex SHA (branch/tag refs are mutable and can skew between jobs mid-run). The value is passed viaenv:, never interpolated into the script body.cursor-review.ymlheader example (workflows_ref: main→<same-sha-as-uses>, and itsuses: …@main→@<sha> # v1), theagents-md-integrity.ymlheader ("Defaultmain" → REQUIRED),.github/cursor-review/README.md's input table, plus new notes inREADME.mdandAGENTS.md..github/workflow-pins/— a stdlib-only, text-level checker (no PyYAML in this repo), wired up astest-workflow-pins.yml. It fails if anyon: workflow_callworkflow (a) declares adefault:for itsworkflows_refinput, in block or flow-mapping form, or (b) checks out atref: ${{ inputs.workflows_ref }}in a job that does not run the empty-ref guard first. (b) was added in review: the default is only half the hole, since a new job reopens theref: ''fallback with a default-only lint still green — there was never adefault:to find. A file that uses the input but whose declaration the parser cannot locate is a hard error, not a silent skip. 32-case unit suite plus two CLI smoke tests (a reintroduced default, and an unguarded checkout) proving each really exits non-zero.Why the guard is mandatory, not belt-and-braces
GitHub does not enforce
required: trueforworkflow_callinputs. An omitted input arrives as'', andactions/checkoutwithref: ''silently checks out the default branch — recreating the exact hole.required: trueis documentation; the guard is the enforcement.Falsification of the "this breaks nobody" claim
This change denies a previously-working configuration (omitting
workflows_ref), so I went and looked instead of trusting the premise. GitHub code search surfaces 42 caller files across org repos, private repos my token can read, and six external forks/mirrors (Windecay/SimpAI_Studio,ussoewwin/*,BossGao0808/AstroTexAI,iho6/photoreal_ai_app,kylevirtuous1211/*,Sifr-r/Documents). All pass an explicit 40-hexworkflows_ref. Zero break. Re-audited during review with indent-aware job-block parsing: 38 real caller jobs across 23 repos, all passing the input explicitly (the earlier count included this repo's own header examples and test fixtures). Zero break, confirmed twice. This corroborates the BE-5543 spike independently — no bump-callers rollout needed, no tag action.Judgment calls
pr-size.ymlhad the same hole; UPDATE — it's now fixed, not exempted. It shipped as a fourthworkflow_callworkflow withworkflows_ref/default: main. The original round of this PR left it in the lint'sKNOWN_EXEMPT(caller fleet unaudited, so dropping the default was an unverified break of a required status check downstream) and filed BE-5856 to audit it. That audit happened:vars.PR_SIZE_CALLERShas exactly one entry, its caller already passesworkflows_refSHA-matched to itsuses:pin, and the check is not a required status on that repo's default branch — so dropping the default breaks nobody.pr-size.ymlnow carriesrequired: true, no default, and the standard guard (same as the other four), andKNOWN_EXEMPTis empty — the frozenset stays empty rather than growing new entries.refresh-reviewers.ymlpicked up the guard too, with no compatibility question. It's a workflowmainadded after this branch forked, already shippingworkflows_refasrequired: truewith no default from day one — reconciling this branch withmain(merge commit) surfaced that its assets checkout had no empty-ref guard ahead of it (main doesn't carry this PR's guard convention). Added the same guard step; norequired/defaultchange, so no caller-breakage question to audit — this is a straight security-gap close, not an interface change.uses: Comfy-Org/github-workflows/.github/actions/…@<ref>— the very ref being validated — and a job can'tuses: ./…before its checkout. Making the check depend on the thing it checks is worse than the duplication; noted in.github/workflow-pins/README.mdso the next reviewer doesn't "simplify" it.needs:graph changes later.v1predates all three workflows (it carries onlydetect-unreviewed-merge.yml) and no caller consumes tags.bump-callers.shis untouched — the pin shape is unchanged.ci-groom.yml'sREPLACE_AT_MERGE_WITH_THIS_PRS_SQUASH_SHAplaceholder is a pre-existing breakage owned by its own ticket; left alone. It is non-empty, so it takes the new SHA-shape warning rather than the hard failure.AGENTS.mdgrew by 6 lines and is now exactly 150 — at, not over, its aspirational target (hard ceiling 200).Testing
actionlintclean on all 24 workflow files (it runsshellcheckover everyrun:block, so the new guard shell is shellcheck-clean).New suite: 32 tests, covering the positive/negative fixtures plus the block-boundary cases text parsing gets wrong — a
workflows_refmentioned in a header comment, a caller'swith:value of the same name, adefault:belonging to the next input, inlineon: [push], a quoted"on":key, and stale-exemption detection. One test lints this repo's own checked-in tree.Existing suites all green: cursor-review 40, agents-md-integrity 18, groom 170, bump-callers 123 (+
shellcheck -x), andcheck_agents_md.py --root .passes.Self-CI is unaffected: this repo's own callers (
ci-cursor-review.yml,ci-groom.yml) both passworkflows_refexplicitly and pin an already-merged SHA of the reusable, not this PR's head.Merged
mainin (BE-5546 vs. main drift).mainmoved substantially since this branch forked:cursor-review.ymlgainedpreflight/ledgerjobs and adiff-sizejob that checks out a sharedcheck-pr-sizetool atref: ${{ inputs.workflows_ref }}with no guard (main doesn't know this PR's guard convention — added one, matching the other 16 sites);extra_generated_globs/extra_lockfilesinputs were added alongsidediff_excludes(kept both in the merged docs); andpr-size.yml/refresh-reviewers.yml/bump-callers/groompicked up unrelated fixes. Reproduced the gap locally by merging and re-running the workflow-pins suite before touching anything (it failed 3/77 pointing at the exact new unguarded site), fixed it, and updated the self-test's hardcoded guarded-site count (15 → 16 across the four files it enumerates) to match. Every suite in the repo is green post-merge: cursor-review (159), workflow-pins (78, incl. the new test below), agents-md-integrity (46), groom (365), refresh-reviewers (48), bump-callers + preflight shell suites (185 + 107) andshellcheck -x, pr-risk's shell suites (377), andcheck-pr-size's Go tests.Closed a second, independently-reported guard gap: a comment-only
continue-on-error:line. CodeRabbit flagged thatcontinue-on-error: # notewith the realtrue/falseon the next, more-indented line was read byis_guard_stepas an empty (= not-disqualifying) value, when Actions actually takes the continuation as the value — a guard that fails the step exactly as designed whilecontinue-on-error: trueon the next line lets the job carry on to the checkout anyway. Fixed to read the continuation line when the inline value is empty/comment-only, with a same-line-trueand continuation-falseregression test.Review round (
a924144): the guard shell was hardened in all 12 copies — it now trims before the empty test (actions/checkoutreadsrefviacore.getInput, which trims, so a whitespace-only value was an empty ref to it while passing a bare-z) and echoes only the stripped value, which also stops a multi-line ref from satisfying the line-orientedgrepon one 40-hex line and from smuggling a::workflow command::into the log. Verified behaviourally against each adversarial input, not by inspection. The guard-coverage check was verified non-vacuous: it finds all 12 sites, and flags all 12 when the guard is stripped.🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.
enabledswitch, off by default #125