[PR #228] [MERGED] fix(workflow-pins): strip comments before classifying a ref-checkout line #235

Closed
opened 2026-10-26 22:37:38 +00:00 by bsakaguchi · 0 comments
Owner

📋 Pull Request Information

Original PR: https://github.com/Comfy-Org/github-workflows/pull/228
Author: @mattmillerai
Created: 8/26/2026
Status: ✅ Merged
Merged: 8/26/2026
Merged by: @mattmillerai

Base: main ← Head: matt/be-9129-workflow-pins-strip-comment


📝 Commits (6)

  • cc49579 fix(workflow-pins): classify ref-checkout lines on comment-stripped text
  • 29585be docs(workflow-pins): note that classification ignores # comments
  • d665bfe fix(workflow-pins): only strip a # where it really opens a comment (BE-9129)
  • 1e8a5a3 fix(workflow-pins): open the ref: window for a quoted scalar split after its opening text (BE-9129)
  • f43738e fix(workflow-pins): carry quote state across lines and open every multi-line ref: window (BE-9129)
  • 7a3f802 fix(workflow-pins): bound the carried quote state and judge every split ref: on its fold (BE-9129)

📊 Changes

3 files changed (+1045 additions, -38 deletions)

View changed files

📝 .github/workflow-pins/README.md (+74 -0)
📝 .github/workflow-pins/check_workflow_pins.py (+370 -38)
📝 .github/workflow-pins/tests/test_check_workflow_pins.py (+601 -0)

📄 Description

ELI-5

The workflow-pin lint reads each line of a workflow file and decides what it is:
a WORKFLOWS_REF binding, a checkout at the input, or a checkout at a step's
output. It was making that decision on the raw line — comment and all. So if
you wrote a perfectly correct, if:-guarded checkout and added a helpful note
after it:

ref: ${{ steps.r.outputs.ref }}  # resolved from inputs.workflows_ref

…the .* in _REF_USE_BLOCK_RE swallowed the comment, the line looked like a
direct checkout at the input, and CI failed a compliant workflow. The same bug
runs the other way: a comment that merely spells out {WORKFLOWS_REF: "${{ inputs.workflows_ref }}"} registered a resolver step that does not exist.

The fix is to classify on the comment-stripped text. A comment is prose; it
configures nothing, so it now changes no verdict.

What changed

ref_checkouts in .github/workflow-pins/check_workflow_pins.py, three call
sites:

  1. Arm selection now reads code = _strip_comment(line) — _GUARD_BINDING_RE,
    is_ref_use, and _GUARD_BINDING_FLOW_RE all take code.
  2. The multi-line ref: continuation path takes
    mention_re.search(_strip_comment(line)).
  3. Everything else in that loop keeps the RAW line — _indent,
    _REF_KEY_OPEN_RE, _steps_output_sites, _pins_to_job_workflow_sha,
    _leading_operand_reaches_input, _binding_step_id, is_guard_step — because
    _strip_comment also drops leading indentation, and those readers judge by
    column. (_pins_to_job_workflow_sha and _leading_operand_reaches_input
    already strip internally, so they were already consistent.)

is_ref_use itself is untouched. Plus a README paragraph documenting the rule,
and 7 tests.

Verification

The 4 bug directions, before → after:

shape before after
guarded step-output ref: + explanatory comment (False, False, False), 1 unguarded (False, True, True), 0 unguarded
same, continuation/block-scalar spelling (False, False, False), 1 unguarded (False, True, True), 0 unguarded
comment-as-flow-binding decoy [(…, False, False, True)] []
ref: main # {WORKFLOWS_REF: …} [(…, False, False, False)] []

Fail-closed directions are pinned, not assumed. Three of the seven new tests
pass both before and after by design — they exist to prove the strip costs
nothing:

  • a real env: binding carrying a trailing # bound still guards its job;
  • an unguarded ref: ${{ inputs.workflows_ref }} # note is still reported,
    in both the same-line and continuation spellings;
  • a run: | body line (echo "ref: ${{ … }} # PR #${n}") records no site
    before or after — pinning the over-protection _strip_comment's own docstring
    documents.

Removing the source fix and re-running: exactly 4 of the 7 new tests fail —
the 4 bug directions — and the 3 fail-closed pins stay green. Tests that pass in
both states would have been worthless here, so this was checked rather than
inferred.

Corpus sweep. Ran ref_checkouts over all 48 workflow files in
.github/workflows/, pre-fix and post-fix, and diffed the full per-file site
tuples: byte-identical. 26 judged ref-checkout sites across 11 workflows,
all guarded, both ways. Also swept the tree for the bug shape itself —
0 ref: lines carry a trailing comment, 0 WORKFLOWS_REF bindings do, and the
1 line mentioning inputs.workflows_ref inside a comment is ordinary prose in a
run: body that records nothing. So this is a latent false red today: it
fires the first time someone documents a ref: line, which the README arguably
invites.

Adversarial probe battery. Because the risk here is fail-open — a strip
hiding a real checkout — I ran 21 hand-built shapes through both versions and
diffed: unguarded block/flow/continuation/fallback checkouts, a leading-falsey
|| operand, a dangling step output, an UNPARSED value, a flow mapping with an
apostrophe in a sibling scalar, a curl -d '{"ref": …}' line in a run: body,
the quoted-scalar decoy, and env_aliases on a commented-out alias. No
difference on any of them
, and 19 of the 21 record a real site, so the diff is
not vacuous.

Commands run:

  • python3 -m unittest discover -s .github/workflow-pins/tests -p 'test_*.py' — 279 passed, 0 failed (272 before, +7)
  • 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)
  • The three test-workflow-pins.yml CI smoke tests, run locally: reintroduced-default still rejected, unguarded checkout still rejected, unreadable-steps: still warns-without-failing (exit 0 + ::warning)
  • python3 .github/agents-md-integrity/check_agents_md.py --root . — passed (1 pre-existing unrelated CODEOWNERS warning)
  • python3 -m py_compile on both touched files

Riskiest line

ref_use = binding is None and is_ref_use(code, ref_res). If _strip_comment
ever removed text that is really config, a genuine unguarded checkout would
stop being reported — the one direction this lint must never move. Three things
make it safe: an unquoted # in YAML is a comment, so there is nothing
real past it; _strip_comment is quote-aware and deliberately
over-protective (any quote opens a masked region), so it strips less than a
naive regex and is biased toward keeping text visible; and the probe battery
above shows no real shape flipping from reported to unreported.

The decoy tests assert [] where a site used to be recorded, which reads like a
capability being taken away — it isn't. The binding in those fixtures lives
entirely inside a YAML comment, which Actions never evaluates, so there is no
runtime resolver and nothing real goes unguarded; the assertion is checked
against the same steps with the comment deleted, which produce the identical
[].

Review round 1 — two fail-open regressions closed (d665bfe)

The panel found that classifying on _strip_comment(line) unconditionally
moved the lint fail-OPEN in the two places a # is not a comment at all — the
one direction ref-use recognition may never move. Both were reproduced against
the base before fixing, and both now match base behaviour exactly:

shape base first cut now
ref: >- / ${{ 'a / b # c' && inputs.workflows_ref }} 1 site 0 sites 1 site
- {name: "foo / bar # baz", …, with: {ref: …}} 1 site 0 sites 1 site
  1. A # inside a block or quoted scalar is content, not a comment.
    _REF_KEY_OPEN_RE now captures which spelling opened the scalar
    (quoted / block), and only the plain ref:-then-value form — the one
    YAML really ends at a # — has its continuations stripped. pending_parts
    follows the same gate, so the folded step-output read cannot drift from it.
  2. A quote left open by the previous line suspends the strip.
    _strip_comment rescans each physical line from scratch, so the tail of a
    flow mapping split across lines had its closing quote's # read as an
    opener and the checkout truncated away. ref_checkouts now tracks the
    previous line's end-of-line quote state on the strict _quote_mask reading
    — the one _flow_brace_delta takes for the same structural question — and
    judges RAW when it was left open. That is the pre-strip reading, i.e. the
    fail-closed one.

Both gates are pinned by tests that fail on the previous commit
(test_a_hash_inside_a_block_scalar_body_is_content_not_a_comment,
test_a_quote_opened_on_the_previous_line_suspends_the_strip), plus two
complements that pass either way so the gates cannot be read as "never strip a
continuation": the quoted-scalar case, and the plain ref: continuation whose
comment is still stripped.

Re-verified after the change: 283 tests pass; the 48-file corpus sweep is
byte-identical across base / first cut / now; a 21-shape battery diffed against
both earlier versions changes on exactly these two shapes and nothing else. The
only shapes where the branch still records fewer sites than base are the two
comment-only decoys it set out to fix, where no runtime site exists.

Review round 2

Eight panel findings on d665bfe/1e8a5a3, all reproduced before acting, each
diffed against main to tell introduced from pre-existing (f43738e):

# finding verdict action
High quote state re-derived per line — 3+-line flow mapping tail stripped regression vs main fixed: _quote_scan carries the open quote line to line (strict reading)
High quote-type-blind scan closes a region early not reproducible — _quote_mask matches quote types; the line is reported on both trees replied with the evidence
Med non-plain continuation keeps # … inputs.workflows_ref as value YAML-correct: no comment exists inside |/>/quoted bodies; judged as the same words on one line without the # replied; README claim corrected; two-spellings-agree test; guard half is BE-9637
Med step-output half of the continuation re-strips the raw line pre-existing, same axis fixed: _steps_output_sites(strip=False) reads the gated text
Med strict reading blind to a quote behind &anchor/!!tag regression vs main fixed at the source: _opens_quoted_scalar walks over node properties
Med ref: window skips >2- and a plain same-line RHS pre-existing, on the edited pattern fixed: shared _BLOCK_HEADER; plain window seeded from the key line
Med WORKFLOWS_REF: in a run: body registers a binding pre-existing, different axis deferred to a follow-up (design choice: env:-parent check vs skipping block-scalar bodies)
Low the plain gate was unpinned (suite passed with it deleted) confirmed test added with the suggested fixture

Every fix reverted in turn fails at least one new test (mutation-checked);
ref_checkouts over all 48 workflow files is byte-identical across main,
the previous head and f43738e.

Review round 3 (7a3f802)

Ten panel findings on f43738e, every one reproduced on the branch head before
acting, and every fix pinned by a test that fails on f43738e (9 new tests fail
there; the tenth is the fixture drift guard):

# finding action
High carried quote state never resynchronizes — one stray quote in a run: line/body disables the windows and the strip for the rest of the job fixed: the state is bounded by the opener's indentation (YAML continues a scalar only onto deeper lines), so it resets at the opener's own column
High quoted window seeded empty — "${{ steps.r.outputs.ref / }}" records nothing, "${{ / steps.r.outputs.ref }}" is UNPARSED fixed: seeded with the key line's post-quote text; both now give the guarded one-line verdict
Med a quote a trailing comment leaves open opens the window on a finished ref fixed: _open_quoted_value judges the ref value's own quote on the scan mask
Med alias opener misses the two split spellings ref: gained fixed: _env_bindings folds a quoted-open or plain env value with its deeper continuations
Med input mention judged per physical line (inputs[ / 'workflows_ref']) fixed: judged on the fold
Med key-line step output continues past a still-open ${{ fixed: an unclosed value with a deeper line below waits for the fold
Med leading-operand check missing on the continuation arm (re-raise) fixed here after all — the new plain window routes new spellings into it; _leading_operand_reaches_input(strip=False) runs on the fold, so the split spelling matches the one-line verdict (this covers BE-9637's premise)
Med text past a carried scalar's closing quote kept raw (re-raise) fixed: _strip_after_carried_quote strips the remainder once the scalar closes
Low recursive node-property walk hits the recursion limit fixed: while loop
Nit QUOTED_SPLIT_CHECKOUT has no drift guard fixed

ref_checkouts and env_aliases over all 48 workflow files: byte-identical
between main and 7a3f802.

Residual

Deliberately not fixed here, each stated so it is actionable without this PR
open:

  • Quoted-scalar decoys. name: "{ref: ${{ inputs.workflows_ref }}}" still
    classifies as a ref use and is reported unguarded. That is a
    _REF_USE_FLOW_RE / quote-mask question rather than a comment question, it
    fails closed (a spurious failure, not a missed hole), and it is
    out of scope for this change. Confirmed still present after the fix by direct
    probe. 0 occurrences in the 48 real workflow files.
  • Flow-mapping if:. An if: written inside a flow mapping is still not
    read, so such a step is reported unguarded even when guarded at runtime —
    pre-existing, documented in .github/workflow-pins/README.md, untouched.
  • Unexercised artifact. The upstream read-only investigation's findings note
    — where the evidence behind this change was originally gathered — lives in an
    issue tracker this environment has no access to, so it was not read. The
    fix was instead re-derived from scratch against the checked-in checker: each of
    the four failure modes was reproduced on this branch's base commit before any
    edit, and each is now pinned by a test that fails without the fix. The
    original run's transcript is likewise unreadable from here.
  • One pre-existing defect in the ref: continuation arm is still deferred
    (BE-9638): it judges _pins_to_job_workflow_sha on one physical line, so a
    fallback folded across two lines loses uses_fallback and, downstream,
    check_dir's default: '' carve-out (fails closed; low). Its sibling —
    the missing leading-operand check, BE-9637 — was fixed in round 3 once the
    new plain window started routing fresh spellings into that arm.
  • The apostrophe caveat. _strip_comment opens a masked region on any
    quote, so name: don't resolve # {WORKFLOWS_REF: …} leaves the comment
    unstripped and read as config. Verified in both directions; both fail
    closed (a spurious report, never a missed checkout), and the
    over-protection is what keeps a run: | body's echo "PR #${n}" intact.
    Documented in the README rather than narrowed.
  • Carried quote state past a re-opened quote on the same line. The
    indentation bound is taken from the line that first opened the scalar; a
    continuation that closes one quote and opens another keeps that bound rather
    than its own. Over-approximates in the fail-closed direction (the raw reading
    persists a line longer), only inside the original opener's block.
  • --workflows-dir against consumer repos. The CLI was exercised only
    against this repo's own tree and synthetic temp fixtures, not against any
    consumer repo's workflows — consumer rosters are private and out of reach from
    a public-repo change.
  • Cross-checked, found clean rather than deferred: env_aliases does not
    have the analogous phantom-alias bug (a commented-out BASE: ${{ inputs.workflows_ref }} registers no alias, verified by probe), and
    ref_checkouts is the only caller of _GUARD_BINDING_RE / is_ref_use /
    _GUARD_BINDING_FLOW_RE, so there is no second call site carrying the same
    raw-line classification.

Judgment calls

  • README updated. The design doc already stated that the fallback matchers
    and env_aliases strip comments; the _REF_USE_* trio's section did not. The
    change would otherwise have left the doc silently half-true, so a short
    paragraph was added rather than leaving it as a follow-up.
  • A comment was added at each of the two stripped call sites, matching this
    file's unusually high comment density and naming why every other reader in
    the loop stays raw. Future edits to that loop are the realistic way this
    regresses.
  • The new fixtures are deliberately smaller than the existing RESOLVER —
    the comment, not the guard shape, is what is under test, and the larger fixture
    would have made the assertions harder to read.

Provenance

  • Authored by: agent-work loop
  • Verified: python3 -m unittest discover -s .github/workflow-pins/tests -p 'test_*.py': 308 passed, 0 failed (272 before the branch, +36); python3 .github/workflow-pins/check_workflow_pins.py: OK, 11 workflows, 0 exempt; check_agents_md.py --root .: passed (1 pre-existing CODEOWNERS warning); ref_checkouts swept over all 48 workflow files at main / the previous head (1e8a5a3) / f43738e: byte-identical all three ways, and ref_checkouts + env_aliases over the same 48 files identical between main and 7a3f802; the 9 behavioural round-3 tests all fail on f43738e and pass on 7a3f802; each round-2 fix reverted in turn -> at least one of the new tests fails (sticky quote state, node properties, strip=False, plain opener, block header, the plain gate, the quoted_open carried-state gate); all 8 round-2 findings reproduced against both main and the branch before acting; the merged result against current main (clean auto-merge) passes the suite and the repo lint
  • Deviations: none in the original plan. Round 1 added two fail-open regressions the first cut introduced (d665bfe); round 2 (f43738e) fixed two more regressions the round-1 fix left (carried quote state, node properties), closed three pre-existing gaps on the same axis or the same edited pattern (step-output re-strip, >2-, plain same-line RHS), refuted one finding with evidence, answered one as YAML-correct with a README correction, and deferred one pre-existing structural finding to a follow-up. Round 3 (7a3f802) fixed all ten findings, including the previously-deferred leading-operand check (BE-9637) once the new plain window made it reachable through code this PR adds; no new deferrals. All twenty-three review threads answered and resolved. See ## Review round 1, ## Review round 2 and ## Residual.

🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.

## 📋 Pull Request Information **Original PR:** https://github.com/Comfy-Org/github-workflows/pull/228 **Author:** [@mattmillerai](https://github.com/mattmillerai) **Created:** 8/26/2026 **Status:** ✅ Merged **Merged:** 8/26/2026 **Merged by:** [@mattmillerai](https://github.com/mattmillerai) **Base:** `main` ← **Head:** `matt/be-9129-workflow-pins-strip-comment` --- ### 📝 Commits (6) - [`cc49579`](https://github.com/Comfy-Org/github-workflows/commit/cc49579af82df2d9d57c45cc3bf9a9e704397dc7) fix(workflow-pins): classify ref-checkout lines on comment-stripped text - [`29585be`](https://github.com/Comfy-Org/github-workflows/commit/29585be72c597d91ce5ad505669fc2ab9b67846b) docs(workflow-pins): note that classification ignores `#` comments - [`d665bfe`](https://github.com/Comfy-Org/github-workflows/commit/d665bfe2638f5d910382d2b8303537a38eb8808e) fix(workflow-pins): only strip a `#` where it really opens a comment (BE-9129) - [`1e8a5a3`](https://github.com/Comfy-Org/github-workflows/commit/1e8a5a3bf742099466d0c322507cdf73373726ea) fix(workflow-pins): open the ref: window for a quoted scalar split after its opening text (BE-9129) - [`f43738e`](https://github.com/Comfy-Org/github-workflows/commit/f43738eb96248edb05619c246166a1ef245797bb) fix(workflow-pins): carry quote state across lines and open every multi-line ref: window (BE-9129) - [`7a3f802`](https://github.com/Comfy-Org/github-workflows/commit/7a3f802093ee9115d0c0426ee51f36809634c660) fix(workflow-pins): bound the carried quote state and judge every split ref: on its fold (BE-9129) ### 📊 Changes **3 files changed** (+1045 additions, -38 deletions) <details> <summary>View changed files</summary> 📝 `.github/workflow-pins/README.md` (+74 -0) 📝 `.github/workflow-pins/check_workflow_pins.py` (+370 -38) 📝 `.github/workflow-pins/tests/test_check_workflow_pins.py` (+601 -0) </details> ### 📄 Description ## ELI-5 The workflow-pin lint reads each line of a workflow file and decides what it is: a `WORKFLOWS_REF` binding, a checkout at the input, or a checkout at a step's output. It was making that decision on the **raw** line — comment and all. So if you wrote a perfectly correct, `if:`-guarded checkout and added a helpful note after it: ```yaml ref: ${{ steps.r.outputs.ref }} # resolved from inputs.workflows_ref ``` …the `.*` in `_REF_USE_BLOCK_RE` swallowed the comment, the line looked like a direct checkout at the input, and CI failed a compliant workflow. The same bug runs the other way: a comment that merely *spells out* `{WORKFLOWS_REF: "${{ inputs.workflows_ref }}"}` registered a resolver step that does not exist. The fix is to classify on the comment-stripped text. A comment is prose; it configures nothing, so it now changes no verdict. ## What changed `ref_checkouts` in `.github/workflow-pins/check_workflow_pins.py`, three call sites: 1. Arm selection now reads `code = _strip_comment(line)` — `_GUARD_BINDING_RE`, `is_ref_use`, and `_GUARD_BINDING_FLOW_RE` all take `code`. 2. The multi-line `ref:` continuation path takes `mention_re.search(_strip_comment(line))`. 3. **Everything else in that loop keeps the RAW line** — `_indent`, `_REF_KEY_OPEN_RE`, `_steps_output_sites`, `_pins_to_job_workflow_sha`, `_leading_operand_reaches_input`, `_binding_step_id`, `is_guard_step` — because `_strip_comment` also drops leading indentation, and those readers judge by column. (`_pins_to_job_workflow_sha` and `_leading_operand_reaches_input` already strip internally, so they were already consistent.) `is_ref_use` itself is untouched. Plus a README paragraph documenting the rule, and 7 tests. ## Verification The 4 bug directions, before → after: | shape | before | after | | --- | --- | --- | | guarded step-output `ref:` + explanatory comment | `(False, False, False)`, 1 unguarded | `(False, True, True)`, 0 unguarded | | same, continuation/block-scalar spelling | `(False, False, False)`, 1 unguarded | `(False, True, True)`, 0 unguarded | | comment-as-flow-binding decoy | `[(…, False, False, True)]` | `[]` | | `ref: main # {WORKFLOWS_REF: …}` | `[(…, False, False, False)]` | `[]` | **Fail-closed directions are pinned, not assumed.** Three of the seven new tests pass both before and after by design — they exist to prove the strip costs nothing: - a real `env:` binding carrying a trailing `# bound` still guards its job; - an **unguarded** `ref: ${{ inputs.workflows_ref }} # note` is still reported, in both the same-line and continuation spellings; - a `run: |` body line (`echo "ref: ${{ … }} # PR #${n}"`) records no site before or after — pinning the over-protection `_strip_comment`'s own docstring documents. Removing the source fix and re-running: exactly **4 of the 7** new tests fail — the 4 bug directions — and the 3 fail-closed pins stay green. Tests that pass in both states would have been worthless here, so this was checked rather than inferred. **Corpus sweep.** Ran `ref_checkouts` over all 48 workflow files in `.github/workflows/`, pre-fix and post-fix, and diffed the full per-file site tuples: **byte-identical**. 26 judged ref-checkout sites across 11 workflows, all guarded, both ways. Also swept the tree for the bug shape itself — 0 `ref:` lines carry a trailing comment, 0 `WORKFLOWS_REF` bindings do, and the 1 line mentioning `inputs.workflows_ref` inside a comment is ordinary prose in a `run:` body that records nothing. So this is a **latent** false red today: it fires the first time someone documents a `ref:` line, which the README arguably invites. **Adversarial probe battery.** Because the risk here is fail-*open* — a strip hiding a real checkout — I ran 21 hand-built shapes through both versions and diffed: unguarded block/flow/continuation/fallback checkouts, a leading-falsey `||` operand, a dangling step output, an `UNPARSED` value, a flow mapping with an apostrophe in a sibling scalar, a `curl -d '{"ref": …}'` line in a `run:` body, the quoted-scalar decoy, and `env_aliases` on a commented-out alias. **No difference on any of them**, and 19 of the 21 record a real site, so the diff is not vacuous. Commands run: - `python3 -m unittest discover -s .github/workflow-pins/tests -p 'test_*.py'` — 279 passed, 0 failed (272 before, +7) - `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)` - The three `test-workflow-pins.yml` CI smoke tests, run locally: reintroduced-default still rejected, unguarded checkout still rejected, unreadable-`steps:` still warns-without-failing (exit 0 + `::warning`) - `python3 .github/agents-md-integrity/check_agents_md.py --root .` — passed (1 pre-existing unrelated CODEOWNERS warning) - `python3 -m py_compile` on both touched files ## Riskiest line `ref_use = binding is None and is_ref_use(code, ref_res)`. If `_strip_comment` ever removed text that is really *config*, a genuine unguarded checkout would stop being reported — the one direction this lint must never move. Three things make it safe: an unquoted ` #` in YAML **is** a comment, so there is nothing real past it; `_strip_comment` is quote-aware and deliberately over-protective (any quote opens a masked region), so it strips *less* than a naive regex and is biased toward keeping text visible; and the probe battery above shows no real shape flipping from reported to unreported. The decoy tests assert `[]` where a site used to be recorded, which reads like a capability being taken away — it isn't. The binding in those fixtures lives entirely inside a YAML comment, which Actions never evaluates, so there is no runtime resolver and nothing real goes unguarded; the assertion is checked against the same steps with the comment deleted, which produce the identical `[]`. ## Review round 1 — two fail-open regressions closed (d665bfe) The panel found that classifying on `_strip_comment(line)` **unconditionally** moved the lint fail-OPEN in the two places a `#` is not a comment at all — the one direction ref-use recognition may never move. Both were reproduced against the base before fixing, and both now match base behaviour exactly: | shape | base | first cut | now | | --- | --- | --- | --- | | `ref: >-` / `${{ 'a` / `b # c' && inputs.workflows_ref }}` | 1 site | **0 sites** | 1 site | | `- {name: "foo` / `bar # baz", …, with: {ref: …}}` | 1 site | **0 sites** | 1 site | 1. **A `#` inside a block or quoted scalar is content, not a comment.** `_REF_KEY_OPEN_RE` now captures *which* spelling opened the scalar (`quoted` / `block`), and only the plain `ref:`-then-value form — the one YAML really ends at a ` #` — has its continuations stripped. `pending_parts` follows the same gate, so the folded step-output read cannot drift from it. 2. **A quote left open by the previous line suspends the strip.** `_strip_comment` rescans each physical line from scratch, so the tail of a flow mapping split across lines had its closing quote's `#` read as an opener and the checkout truncated away. `ref_checkouts` now tracks the previous line's end-of-line quote state on the strict `_quote_mask` reading — the one `_flow_brace_delta` takes for the same structural question — and judges RAW when it was left open. That is the pre-strip reading, i.e. the fail-closed one. Both gates are pinned by tests that **fail on the previous commit** (`test_a_hash_inside_a_block_scalar_body_is_content_not_a_comment`, `test_a_quote_opened_on_the_previous_line_suspends_the_strip`), plus two complements that pass either way so the gates cannot be read as "never strip a continuation": the quoted-scalar case, and the plain `ref:` continuation whose comment *is* still stripped. Re-verified after the change: 283 tests pass; the 48-file corpus sweep is byte-identical across base / first cut / now; a 21-shape battery diffed against both earlier versions changes on exactly these two shapes and nothing else. The only shapes where the branch still records fewer sites than base are the two comment-only decoys it set out to fix, where no runtime site exists. ## Review round 2 Eight panel findings on d665bfe/1e8a5a3, all reproduced before acting, each diffed against `main` to tell introduced from pre-existing (f43738e): | # | finding | verdict | action | | --- | --- | --- | --- | | High | quote state re-derived per line — 3+-line flow mapping tail stripped | regression vs `main` | fixed: `_quote_scan` carries the open quote line to line (strict reading) | | High | quote-type-blind scan closes a region early | **not reproducible** — `_quote_mask` matches quote types; the line is reported on both trees | replied with the evidence | | Med | non-plain continuation keeps `# … inputs.workflows_ref` as value | YAML-correct: no comment exists inside `\|`/`>`/quoted bodies; judged as the same words on one line without the `#` | replied; README claim corrected; two-spellings-agree test; guard half is BE-9637 | | Med | step-output half of the continuation re-strips the raw line | pre-existing, same axis | fixed: `_steps_output_sites(strip=False)` reads the gated text | | Med | strict reading blind to a quote behind `&anchor`/`!!tag` | regression vs `main` | fixed at the source: `_opens_quoted_scalar` walks over node properties | | Med | `ref:` window skips `>2-` and a plain same-line RHS | pre-existing, on the edited pattern | fixed: shared `_BLOCK_HEADER`; plain window seeded from the key line | | Med | `WORKFLOWS_REF:` in a `run:` body registers a binding | pre-existing, different axis | deferred to a follow-up (design choice: `env:`-parent check vs skipping block-scalar bodies) | | Low | the `plain` gate was unpinned (suite passed with it deleted) | confirmed | test added with the suggested fixture | Every fix reverted in turn fails at least one new test (mutation-checked); `ref_checkouts` over all 48 workflow files is byte-identical across `main`, the previous head and f43738e. ## Review round 3 (7a3f802) Ten panel findings on f43738e, every one reproduced on the branch head before acting, and every fix pinned by a test that fails on f43738e (9 new tests fail there; the tenth is the fixture drift guard): | # | finding | action | | --- | --- | --- | | High | carried quote state never resynchronizes — one stray quote in a `run:` line/body disables the windows and the strip for the rest of the job | fixed: the state is bounded by the opener's indentation (YAML continues a scalar only onto deeper lines), so it resets at the opener's own column | | High | quoted window seeded empty — `"${{ steps.r.outputs.ref` / `}}"` records nothing, `"${{` / `steps.r.outputs.ref }}"` is UNPARSED | fixed: seeded with the key line's post-quote text; both now give the guarded one-line verdict | | Med | a quote a trailing comment leaves open opens the window on a finished ref | fixed: `_open_quoted_value` judges the ref value's own quote on the scan mask | | Med | alias opener misses the two split spellings `ref:` gained | fixed: `_env_bindings` folds a quoted-open or plain env value with its deeper continuations | | Med | input mention judged per physical line (`inputs[` / `'workflows_ref']`) | fixed: judged on the fold | | Med | key-line step output `continue`s past a still-open `${{` | fixed: an unclosed value with a deeper line below waits for the fold | | Med | leading-operand check missing on the continuation arm (re-raise) | fixed here after all — the new plain window routes new spellings into it; `_leading_operand_reaches_input(strip=False)` runs on the fold, so the split spelling matches the one-line verdict (this covers BE-9637's premise) | | Med | text past a carried scalar's closing quote kept raw (re-raise) | fixed: `_strip_after_carried_quote` strips the remainder once the scalar closes | | Low | recursive node-property walk hits the recursion limit | fixed: `while` loop | | Nit | `QUOTED_SPLIT_CHECKOUT` has no drift guard | fixed | `ref_checkouts` and `env_aliases` over all 48 workflow files: byte-identical between `main` and 7a3f802. ## Residual Deliberately **not** fixed here, each stated so it is actionable without this PR open: - **Quoted-scalar decoys.** `name: "{ref: ${{ inputs.workflows_ref }}}"` still classifies as a ref use and is reported unguarded. That is a `_REF_USE_FLOW_RE` / quote-mask question rather than a comment question, it fails **closed** (a spurious failure, not a missed hole), and it is out of scope for this change. Confirmed still present after the fix by direct probe. 0 occurrences in the 48 real workflow files. - **Flow-mapping `if:`.** An `if:` written inside a flow mapping is still not read, so such a step is reported unguarded even when guarded at runtime — pre-existing, documented in `.github/workflow-pins/README.md`, untouched. - **Unexercised artifact.** The upstream read-only investigation's findings note — where the evidence behind this change was originally gathered — lives in an issue tracker this environment has no access to, so it was not read. The fix was instead re-derived from scratch against the checked-in checker: each of the four failure modes was reproduced on this branch's base commit before any edit, and each is now pinned by a test that fails without the fix. The original run's transcript is likewise unreadable from here. - **One pre-existing defect in the `ref:` continuation arm** is still deferred (BE-9638): it judges `_pins_to_job_workflow_sha` on one physical line, so a fallback folded across two lines loses `uses_fallback` and, downstream, `check_dir`'s `default: ''` carve-out (fails **closed**; low). Its sibling — the missing leading-operand check, BE-9637 — was fixed in round 3 once the new plain window started routing fresh spellings into that arm. - **The apostrophe caveat.** `_strip_comment` opens a masked region on *any* quote, so `name: don't resolve # {WORKFLOWS_REF: …}` leaves the comment unstripped and read as config. Verified in both directions; both fail **closed** (a spurious report, never a missed checkout), and the over-protection is what keeps a `run: |` body's `echo "PR #${n}"` intact. Documented in the README rather than narrowed. - **Carried quote state past a re-opened quote on the same line.** The indentation bound is taken from the line that first opened the scalar; a continuation that closes one quote and opens another keeps that bound rather than its own. Over-approximates in the fail-closed direction (the raw reading persists a line longer), only inside the original opener's block. - **`--workflows-dir` against consumer repos.** The CLI was exercised only against this repo's own tree and synthetic temp fixtures, not against any consumer repo's workflows — consumer rosters are private and out of reach from a public-repo change. - **Cross-checked, found clean rather than deferred:** `env_aliases` does not have the analogous phantom-alias bug (a commented-out `BASE: ${{ inputs.workflows_ref }}` registers no alias, verified by probe), and `ref_checkouts` is the only caller of `_GUARD_BINDING_RE` / `is_ref_use` / `_GUARD_BINDING_FLOW_RE`, so there is no second call site carrying the same raw-line classification. ## Judgment calls - **README updated.** The design doc already stated that the fallback matchers and `env_aliases` strip comments; the `_REF_USE_*` trio's section did not. The change would otherwise have left the doc silently half-true, so a short paragraph was added rather than leaving it as a follow-up. - **A comment was added at each of the two stripped call sites**, matching this file's unusually high comment density and naming why every *other* reader in the loop stays raw. Future edits to that loop are the realistic way this regresses. - **The new fixtures are deliberately smaller than the existing `RESOLVER`** — the comment, not the guard shape, is what is under test, and the larger fixture would have made the assertions harder to read. ## Provenance - **Authored by:** agent-work loop - **Verified:** `python3 -m unittest discover -s .github/workflow-pins/tests -p 'test_*.py'`: 308 passed, 0 failed (272 before the branch, +36); `python3 .github/workflow-pins/check_workflow_pins.py`: OK, 11 workflows, 0 exempt; `check_agents_md.py --root .`: passed (1 pre-existing CODEOWNERS warning); `ref_checkouts` swept over all 48 workflow files at `main` / the previous head (1e8a5a3) / f43738e: byte-identical all three ways, and `ref_checkouts` + `env_aliases` over the same 48 files identical between `main` and 7a3f802; the 9 behavioural round-3 tests all fail on f43738e and pass on 7a3f802; each round-2 fix reverted in turn -> at least one of the new tests fails (sticky quote state, node properties, `strip=False`, plain opener, block header, the `plain` gate, the `quoted_open` carried-state gate); all 8 round-2 findings reproduced against both `main` and the branch before acting; the merged result against current `main` (clean auto-merge) passes the suite and the repo lint - **Deviations:** none in the original plan. Round 1 added two fail-open regressions the first cut introduced (d665bfe); round 2 (f43738e) fixed two more regressions the round-1 fix left (carried quote state, node properties), closed three pre-existing gaps on the same axis or the same edited pattern (step-output re-strip, `>2-`, plain same-line RHS), refuted one finding with evidence, answered one as YAML-correct with a README correction, and deferred one pre-existing structural finding to a follow-up. Round 3 (7a3f802) fixed all ten findings, including the previously-deferred leading-operand check (BE-9637) once the new plain window made it reachable through code this PR adds; no new deferrals. All twenty-three review threads answered and resolved. See `## Review round 1`, `## Review round 2` and `## Residual`. --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
bsakaguchi 2026-10-26 22:37:38 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
Comfy-Org/github-workflows#235
No description provided.