mirror of
https://github.com/Comfy-Org/github-workflows.git
synced 2026-11-05 00:17:37 +00:00
[PR #242] [MERGED] fix(workflow-pins): judge the folded ref value for the job.workflow_sha fallback #249
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#249
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/242
Author: @mattmillerai
Created: 8/27/2026
Status: ✅ Merged
Merged: 8/28/2026
Merged by: @mattmillerai
Base:
main← Head:matt/be-9648-workflow-pins-folded-fallback📝 Commits (1)
7b6c92dfix(workflow-pins): judge the FOLDED ref value for the job.workflow_sha fallback📊 Changes
3 files changed (+257 additions, -2 deletions)
View changed files
📝
.github/workflow-pins/README.md(+11 -2)📝
.github/workflow-pins/check_workflow_pins.py(+71 -0)📝
.github/workflow-pins/tests/test_check_workflow_pins.py(+175 -0)📄 Description
ELI-5
A workflow can write its checkout ref across several lines. GitHub glues them back into one string before running, but this lint was reading them one line at a time — so
${{ inputs.workflows_ref ||on one line andjob.workflow_sha }}on the next did not look like the self-pin it actually is. The lint then told a perfectly correct workflow to fix two things that were not broken. Now the lint glues the lines back together first, exactly like GitHub does.What was wrong
ref_checkouts' multi-lineref:continuation arm judgeduses_fallbackfrom ONE physical line (fallback = _pins_to_job_workflow_sha(line)), while the continuation form of_FALLBACK_RESis anchored and needs that text to BE the whole${{ inputs.workflows_ref || job.workflow_sha }}. So a block scalar folded asref: >-/${{ inputs.workflows_ref ||/job.workflow_sha }}recordeduses_fallback=False, and two things went wrong at once: the fallback-strength guard in that job no longer covered the site, so it was reported UNGUARDED with the wrong (bare-input) remedy; and where every site in a file folded that way,check_diralso lost thedefault: ''carve-out and added BE-5546's "delete thedefault:" on top. A file that self-pins and guards correctly got two contradictory errors.Fail-closed and latent — no workflow on
mainfolds the fallback today, so this changes no verdict on the real tree (measured below).The fix, and why not the obvious one
The arm records at the FIRST line that mentions the input. For the trailing split that line is one short of the whole expression, so joining only the lines already seen still answers
False— appending-then-judging" ".join(pending_parts)does not fix this spelling. The fold has to read FORWARD._folded_scalar_fromdoes that: from the recording line it walks down while the interpolation is still open and the scalar has not closed, bounded by theref:key's own column, skipping blank/comment lines the way the walk that feeds it does.Three properties keep it safe. The verdict stays at its own site, so there is no deferred flush and the last field of the last step can never be dropped — the hazard the sibling UNPARSED branch guards separately. The single-line answer is tried first and the join runs only when it said
False, so the one-line spelling behaves byte for byte as it did and the fold can only ever ADD the fallback, never clear one. And the folded text goes to the SAME anchored matcher, so a third operand or a leading operand is rejected in the folded spelling exactly as it is on one line.Judgment call worth flagging: the helper takes a
plainflag and picks_strip_commentvs. a raw.strip()the same way the caller's own continuation branch does, because a#ends a plain scalar's value but is CONTENT inside a|/>body. It changes nothing for today's single consumer (_pins_to_job_workflow_shastrips its own argument regardless) — it matters only for a caller that reads the fold un-stripped, which is why the helper was extracted rather than inlined.Verified
Full suite green (316, up from 308). The five new assertions were run against the unmodified parser first and all five fail there, so they are red→green rather than self-confirming.
test_the_forward_fold_stops_at_the_end_of_the_scalarand the two operand negative controls pass both ways by design — they are regression guards on the bound and the anchor, not on the fix.End-to-end on the artifact the lint exists for: a copy of
groom.ymlwith all seven fallbackref:lines rewritten asref: >-/${{ inputs.workflows_ref ||/job.workflow_sha }}passes--workflows-dir <tmp>cleanly on this branch; onmainthe same file emits 8 problems — the seven unguarded-checkout errors with the bare-input remedy, plus the line-361default:error.Adversarial sweep of spellings the fold must NOT bless, each compared against
mainand against its own one-line twin: a third operand (|| job.workflow_sha || 'main' }}), a concatenation prefix (refs/heads/${{ …), a bare input, and|| 'main'all stay non-fallback and stay reported. A blank line inside the scalar is the one case the fold newly gets right (mainmissed it); it is locked in by a test.Provenance
python3 -m unittest discover -s .github/workflow-pins/tests -p 'test_*.py': 316 passed, 0 failed (308 on the base commit; 5 of the 8 new assertions verified red against the unpatched parser).python3 .github/workflow-pins/check_workflow_pins.py:OK — 11 workflow(s) declare workflows_ref, none with a default, every ref checkout guarded (0 exempt). Pre/postref_checkoutssweep over all 50 files in.github/workflows/: 0 differing files,groom.ymlunchanged at sites 683, 1185, 1660, 2067, 2270, 2514, 3009, all(…, True, True, False). Folded-groom.ymlcopy via--workflows-dir: exit 0 here, exit 1 with 8 problems on the base commit.python3 .github/agents-md-integrity/check_agents_md.py --root .: passed (2 pre-existing warnings, unrelated to this diff).bash .github/bump-callers/tests/test_paths_contract.sh: 70 passed, 0 failed.parts = pending_parts + [cont]becameparts = list(pending_parts)— the current loop already appendscontbefore this point, so re-adding it would have duplicated the line in the fold. And the plan's sibling leading-operand work is already onmain(the arm readsjoinedfor_leading_operand_reaches_inputtoday), so the helper is extracted for reuse but that call site is left untouched.Residual
The same defect on a different arm: the PLAIN multi-line spelling is still misjudged.
ref: ${{ inputs.workflows_ref ||/job.workflow_sha }}— no>-header, just a plain scalar folding onto the line below — is valid YAML that the runtime folds identically, and it still records(…, False, False, False)under a fallback guard. This fix does not reach it and was not meant to: the root cause is different.is_ref_usereturns True on that first physical line even though its${{is unclosed, so the ONE-LINEelif ref_use:arm fires and the continuation window never opens at all. Fixing it means deferring or forward-folding that arm's verdict too, which also moves its_leading_operand_reaches_input(line, …)read — a wider change than the one scoped here, and one whose blast radius is every non-folded checkout rather than only the folded ones. Repro: put that two-lineref:under theGUARD_WITH_FALLBACKfixture inGuardCoverageTestsand compare against the one-line spelling. Same severity as the bug fixed here — latent, fail-closed, no workflow onmainwrites it.A folded leading operand can still pass where its one-line spelling is reported.
ref: >-/${{ 'main' || inputs.workflows_ref ||/job.workflow_sha }}under a BARE guard yields(…, False, True, False)— guarded — while the byte-identical one-line expression correctly yields(…, False, False, False). Cause:_leading_operand_reaches_inputis handed the pre-foldjoined, whose${{is still unclosed, so_INTERPOLATION_REfinds nothing and the check returns True without narrowing. Verified present on the base commit and UNCHANGED by this diff — it is neither introduced nor fixed here._folded_scalar_fromreturns the completed text precisely so this is a one-line follow-up: pass the folded value to that check instead. Left out because it widens an existing guard verdict, which needs its own history read rather than a drive-by.Unexercised artifacts. The plan this PR implements cites an upstream investigation's findings comment as the evidence it was built on, plus an internal dashboard URL and a worker transcript log on the build host (documented as best-effort and truncated by the next run). None were readable from this session — no tracker access, and the other two are internal-network/local paths — so the reproduction here was rebuilt from first principles against the parser rather than inherited: the failing spellings were derived from
_FALLBACK_RES' anchoring and confirmed red against the unpatched code before the fix was written. Two figures the plan quoted have also drifted with the file: it expected a 272-test suite and 48 workflow files; the base commit has 308 tests and 50 files. The site-level expectations it named forgroom.ymlstill hold exactly.🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.