mirror of
https://github.com/Comfy-Org/github-workflows.git
synced 2026-11-05 00:17:37 +00:00
[PR #319] [MERGED] fix(pr-risk): publish the Check Run on a manual dispatch regardless of enabled #319
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#319
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/319
Author: @mattmillerai
Created: 9/18/2026
Status: ✅ Merged
Merged: 9/19/2026
Merged by: @mattmillerai
Base:
main← Head:matt/be-15438-pr-risk-publish-check-dispatch📝 Commits (2)
96cc68dfix(pr-risk): publish the Check Run on a manual dispatch regardless ofenabled71b5eeatest(pr-risk): pin the job gates by equality, and scan the whole publish-check job📊 Changes
5 files changed (+261 additions, -7 deletions)
View changed files
📝
.github/workflows/pr-risk.yml(+21 -2)📝
.github/workflows/test-pr-risk.yml(+6 -1)📝
scripts/pr-risk/resolve-enabled.sh(+7 -4)➕
scripts/pr-risk/tests/test_job_gates.sh(+210 -0)📝
scripts/pr-risk/tests/test_resolve_enabled.sh(+17 -0)📄 Description
ELI-5
A repo can enrol the PR-risk grader switched OFF, then run it by hand on one PR to see what it would say before turning it on for real. That preview graded the PR, labelled it and posted the comment — but not the Check Run, the one permanent, commit-attached record the preview mostly exists to show off. This makes the Check Run come along too.
What changed
publish-checkin.github/workflows/pr-risk.ymlwas gated onneeds.gate.outputs.enabled == 'true' && inputs.check_run && !cancelled(). It is now gated ongradehaving run:needs: [gate, grade]is unchanged,grade's own condition is unchanged, and the hardcodedneutralconclusion is unchanged.The full behaviour table, assuming
check_run: trueand a run that was not cancelled. Exactly one row moves:enabledgrade.resultpull_requestpull_requestpull_requestworkflow_dispatchworkflow_dispatchAll six live callers set
enabled: true, so their behaviour is byte-identical and no consumer pin is bumped in this PR.Why not the shorter
inputs.check_run && !cancelled()!cancelled()is a status check function, and any status function in anif:replaces the implicit "all needs succeeded" condition. With no result test the job would therefore RUN on every disabledpull_requestrun — wheregradewas merely skipped — boot a runner, find an emptysurfaces, publish nothing and exit 0. Green, invisible, and one wasted runner per disabled PR across every enrolled repo, which is precisely the cost the cheapgatejob exists to avoid.needs.grade.result != 'skipped'is what makes the!cancelled()safe.!= 'skipped'rather than== 'success'because the publish step already tolerates a partial render (an emptysurfacesis "nothing to publish", not an error; per-target failures are warnings). Asuccess()-shaped gate would discard the targets that did render whenever grade failed partway.Chesterton's Fence — why the
enabledclause was safe to removeThis changes an existing condition, so the history was read before the edit rather than after:
git blameputs the clause incc03872(#120), the commit that introduced thepublish-checkjob — 2026-08-04 14:57.gradeis0c9c2b5(#116), 2026-08-04 14:22 — 35 minutes earlier.check_runas off-by-default and explains the separate-job split at length; it never mentionsenabled. Neither does the job's own header comment, which discusses onlycheck_runand the unconditionalchecks: writegrant.So #120 copied the older gate shape and did not pick up the exemption #116 had just landed on the job it depends on. The clause was incidental, not a decision, and the two conditions genuinely disagree in exactly one state — the last row of the table above — which is the state #116 created on purpose.
Prose corrected alongside it
publish-check's header comment: one sentence that the job followsgradeon a dispatch regardless ofenabled, plus the rationale inline above theif:.check_runinput description: published whenevergraderan, including a dispatch on a repo whoseenabledresolves false.enabledinput description: its "A MANUALworkflow_dispatchGRADES REGARDLESS" now adds "(and, when opted in, posts the sticky comment and the Check Run)".scripts/pr-risk/resolve-enabled.sh: the disabled-dispatch step summary said the run "will sync arisk:*label". The sticky comment is posted from insidegradeand so already rode along on a disabled dispatch before this PR; the Check Run now does too. The summary names all three, andtest_resolve_enabled.shpins the new clause.71b5eea). The comment is refreshed only by a later re-grade, which a disabled repo never performs, so it keeps showing this moment's tier while subsequent commits go ungraded. It is named in the warning now; the Check Run is called out as carrying no such caveat, being pinned to the commit it graded. Line breaks keepevery later commit will go ungradedintact on one line, since the suite pins that phrase and the summary is built from separateechos. The new assertion was confirmed falsifiable.New structural test
scripts/pr-risk/tests/test_job_gates.sh, modelled ontest_pin_contract.sh— hermetic, reads the workflow as text, no network. A jobif:exists only in the workflow and is evaluated by the runner before any step is dispatched, so no script suite can reach it:resolve-enabled.shcan be green on every input it takes and still have its answer consumed by the wrong job.Reworked in
71b5eeaafter review, which found the first draft proved less than it claimed. Every assertion was a substring-presence needle, and a needle sees a term but never how the terms are joined — so gates meaning the opposite of the pinned contract passed it. Both expressions are now pinned by equality, the same divisiontest_pin_contract.shalready settled on for theworkflows_refguard: the needles stay, but as restatements that name which invariant broke rather than as the defence. Three things the equality does not cover are covered separately —grade's membership inneeds:(without itneeds.grade.resultresolves to empty,'' != 'skipped'is true, and the gate silently degenerates), the forbiddenneeds.gate.outputs.enabledscanned over the whole job body rather than just theif:(a step-levelif:reproduces the bug with the job gate still reading correctly), and the slicer's two boundaries.Those boundaries were fragile in both directions, and neither was hypothetical. Bounding on the job's own
name:key let the slice run on into the following job, concatenating two gates into one blob where either could satisfy a needle; it now bounds on the next 2-space job key. And continuation was matched by six-space indent, so a blank line inside the>-scalar — legal YAML — truncated the expression toneeds.grade.result != 'skipped' &&, over which every negative assertion passed vacuously while the coverage self-check still reported a cheerful non-emptyok. Blank lines are continuations now. The comment strip also had to move ahead of the terminator: this workflow divides its jobs with comment blocks at job indent, and terminating on one truncates the body — a vacuous pass for the whole-job scan, the one assertion with no equality behind it.Negative check, as required, and extended to the review's cases — a test that cannot fail is precisely the defect being fixed here. Seven regressions are caught: the OR-joined gate (3 failures), the inverted
!inputs.check_runopt-in (2),gradedropped fromneeds:(1), a step-levelenabledre-imposition (1), the OLD pre-fix gate (5), the naiveinputs.check_run && !cancelled()(3), andgrade's dispatch exemption dropped (2). Two benign reformats stay green, so the equality is not merely brittle: a blank line inside the scalar, andneeds:rewritten from flow to block style. A further case pairs the step-levelif:with a job-indent comment above it — green under the naive rule order, red now.pr-risk.ymlwas restored from a byte copy after each mutation and confirmed byte-identical to its committed state.Wired into
.github/workflows/test-pr-risk.yml(shellcheck list + its own run step). That workflow'spaths:filter already covers bothscripts/pr-risk/**and.github/workflows/pr-risk.yml, so no filter change was needed.Docs — the
docs/callers/pr-risk.mdbranch takenBranch (b): no docs edit in this PR. #317 is still OPEN, and
mainhas nocheck_runrow in that guide yet (it has noenabledrow either — the word does not appear in the file). Editing nothing here is correct; the corrected sentence was left as a comment on #317 so the row lands stating the right contract. TheREADME.mdcatalog row was checked and needs no change — it already attributes the publish decision tocheck_runand never claims a Check Run requiresenabled: true..github/workflow-pins/tests/test_inputs_docs_drift.pychecks row presence, not content, and needed no change; it was run anyway.Provenance
shellcheck -xon the fulltest-pr-risk.ymlscript list (13 files): clean. All 7scripts/pr-risksuites green after the review round: 52 / 138 / 116 / 22 / 28 / 94 / 29 passed, 0 failed (test_job_gates.sh11 → 22,test_resolve_enabled.sh28 → 29).check_workflow_pins.py: OK (11 workflows declareworkflows_ref, none with a default, every ref checkout guarded; 155uses:pinned).check_agents_md.py --root .: passed, with the 2 pre-existing warnings this PR does not touch (AGENTS.md at 156 lines; no CODEOWNERS).workflow-pinsunittest suite: 499 tests, OK (32 skipped). Negative check on the job-gate suite: 7 regressions caught, 2 benign reformats stay green, detailed above.actionlintwas run onpr-risk.yml+test-pr-risk.ymlfor the first commit (v1.7.12, clean) and is not installed in the environment that produced71b5eea— that commit leavespr-risk.ymlbyte-identical, so the earlier result still stands for the YAML.71b5eeais review response only — it changes no workflow YAML and no grading behaviour; it hardens the new suite and extends one step-summary sentence.Residual
The
docs/callers/pr-risk.mdrow is not fixed by this PR and needs a human to land it. #317 adds acheck_runrow whose paragraph asserts "A Check Run also requiresenabled: true." — a sentence this PR makes false. Nothing in CI catches it:test_inputs_docs_drift.pychecks that a row for each input exists, never what it says, so #317 can merge after this PR with the stale contract and stay green indefinitely. Whichever of the two merges second must apply the replacement sentence, which is quoted verbatim in the comment linked above. If #317 merges first, a one-line follow-up edit to that row onmaincloses it.The behaviour change is verified structurally and by documented runner semantics, not by a live Actions run. The claim that a status check function in an
if:replaces the implicitsuccess()onneeds— which is the whole reason theneeds.grade.resulttest is required rather than optional — comes from GitHub's documented expression semantics and from the shape of the existing gates, not from a dispatch executed against a repo withenabled: false. Exercising it for real needs an enrolled consumer repo and aworkflow_dispatchthat would write a label, a comment and a Check Run to someone's PR, which is a mutating action on a private consumer and was deliberately not performed. The first real dispatch preview after this merges is where the fix is actually observed; if the Check Run still does not appear there, theneeds.grade.resulttest is the line to look at.One artifact behind this change could not be read from the build environment. The originating read-only investigation — the one that established the clause was never a stated decision, and whose findings write-up this change is built on — lives in the team's issue tracker, which is not reachable from here. That premise was therefore re-derived independently from
git blameand the two commit messages (cc03872/0c9c2b5) rather than taken on trust; the Chesterton's Fence section above is that re-derivation, and it agrees. The investigation's own write-up may contain reasoning not reflected here.🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.