Skip to content

fix(pm): read YAML block-scalar run: bodies in dispatch-gates discovery - #8465

Merged
hotlong merged 1 commit into
mainfrom
claude/issue-8410-gates-changeset-derivation
Aug 13, 2026
Merged

fix(pm): read YAML block-scalar run: bodies in dispatch-gates discovery#8465
hotlong merged 1 commit into
mainfrom
claude/issue-8410-gates-changeset-derivation

Conversation

@hotlong

Copy link
Copy Markdown
Contributor

Fixes#8410

What was actually wrong (both hypotheses in the card were refuted)

The card proposed that the Check Changeset enforcement lives inline in pr-automation.ymlrun: blocks, so the watch-hint scan cannot see its .changeset/ path literals. Measured on origin/main: that is not the mechanism.

The enforcement does not live inline at all. It is scripts/check-adr-0087-registration.mjs, a real 162KB script file the scanner is perfectly able to read — and that file already names .changeset in its own source, so the watch-hint match would fire immediately. The matching layer was never broken.

The defect is one layer earlier, in discovery. extractCheckInvocations used /^\s*run:\s*(.+)$/gm — it reads only the text on the run: line itself. A step written as a YAML block scalar puts | on that line and its commands on the following lines, so the extractor collected the string "|" and never saw the commands:

 - name: Require an ADR-0087 disposition on a declared-breaking changesetrun: | node scripts/check-adr-0087-registration.mjs --self-test node scripts/check-adr-0087-registration.mjs --base "$MERGE_BASE"

Measured across the 24 workflow files, six gate families are invoked exclusively from block-scalar bodies and were absent from the derivation entirely — not matched, and not in the "repo-wide / undetermined" bucket either, because a family that is never discovered has no entry to fall into it:

check-adr-0087-registration, check-empty-changeset, check-shard-attestation, check-osv-exemptions, check-test-completeness, check-cross-package-test-inputs

That is precisely the one output shape the script's own header forbids (a gate the derivation cannot mention at all). PR #8399's symptom follows exactly: its declared-breaking changeset derived check-changeset-no-major (written as a one-line run:, line 842, therefore visible) but not check-adr-0087-registration (block-scalar body, therefore invisible) — and the second is the gate that reddened.

So the scope is wider than the card's title: this was never a .changeset/-specific bug. .changeset/ is just where it was first noticed.

Route chosen, and why neither (a) nor (b)

The dispatch offered (a) teach the scanner to scan run: blocks for path literals, or (b) extract the inline step into a scripts/ file. Measurement rules out both:

  • (b) has nothing to extract. The script already exists as a file. The extraction route was premised on the enforcement being inline, which it is not.
  • (a) targets the wrong layer and would over-match. Path literals do not need to come from the workflow — the check script's own source carries them, which is exactly what the "derived, never listed" design intends. Scanning run: bodies for path literals would add a second, noisier source of truth for something already derived correctly.

The fix is instead a one-concept change confined to discovery: read the whole run: value, block-scalar bodies included. Everything downstream (resolve to script files, read watch hints, cover the input path) is untouched, and no list and no path table was added — the 裁决 constraint holds.

Result for the card's own symptom, node scripts/pm/dispatch-gates.mjs .changeset/any-name.md:

beforeafter
families discovered8490
.changeset/ matches57 (adds check-adr-0087-registration, check-empty-changeset)
repo-wide / undetermined2527 (the two zero-hint scripts now land here correctly)

Comment stripping, and the over-match it prevents

Reading block bodies means reading the shell comments inside them, and this repo's workflow bodies discuss gates by name at length (ci.yml's shard job spells check-shard-attestation.mjs inside a comment explaining that gate's own classifier). "Mentions a gate" is not "runs a gate". A naive any-token scan over these files invents four families that exist only in prose — check:adr-links, check:empty-changeset, check:platform-checklist, check:skill-frame-freshness — and the self-test now pins both directions of that.

Only whole-line comments are dropped: a trailing # note after a real command sits on a line whose command still has to be read. Measured both ways on this tree, stripping changes no family's discovery today; it is there so that stops being luck.

The parser stays hand-written rather than adopting the yaml package on purpose: dispatch-gates.mjs imports only node builtins, so a PM can run it in a fresh worktree before pnpm install. That property is load-bearing and was used during this very task — the derivation ran before the worktree had node_modules.

Tests

Self-test extended from 47 to 61 cases, all local (this file has no CI wiring — another card owns that):

  • 7 fixture cases for the parser: literal and folded block bodies, both invocation shapes, a filtered pnpm --filter check inside a body, a blank line not ending a body, a dedented step ending one.
  • 3 cases for runCommandTexts directly (one entry per step, body lines joined, one-liner verbatim).
  • 2 negative cases pinning that a gate named only in a YAML comment between steps, or only in a shell comment inside a body, is not discovered.
  • 4 live cases against the real pr-automation.yml (a fixture proves the parser; only the live file proves this repo's changeset gate is reachable), including the end-to-end chain: a .changeset/ path is covered by the ADR-0087 gate's own hints.

Reverse verification, direction predicted before running: ablating body reading turned exactly the 7 predicted cases red and left the other 7 green, matching the written prediction case-for-case. The fix was committed first, so the restore came out of a real commit and git diff confirmed byte-identity afterwards.

Gates

Named in the dispatch (all green): check:changeset-gate-self-tests, check:node-version, check:required-contexts, check:shard-attestation, check:workflow-status-functions, node scripts/check-changeset-no-major.mjs (self-test plus a real --base scan), node scripts/pm/dispatch-gates.mjs --self-test, check:nul-bytes. Also ran eslint on the changed file (clean).

Re-derived against the actual diff (node scripts/pm/dispatch-gates.mjs scripts/pm/dispatch-gates.mjs): no check family names this path in its own source, so the dispatch list — derived for the pr-automation.yml surface this PR ended up not needing to touch — is a strict superset. Nothing was added to the run.

skip-changeset: this PR changes only PM tooling under scripts/pm/ and releases nothing.


Generated by Claude Code

…ry (#8410)
`extractCheckInvocations` read only the text on the `run:` line itself, so a
step written as a block scalar (`run: |`) contributed the string "|" and none
of its commands. Six gate families are invoked exclusively that way and were
absent from the derivation entirely -- not matched, and not in the
"undetermined" bucket either, since a family that is never discovered has no
entry to fall into it. That is the one output shape the script's contract
forbids, and it cost PR #8399 a CI round: its declared-breaking changeset
derived check-changeset-no-major (a one-line `run:`) but not
check-adr-0087-registration (a block-scalar body), the gate that reddened.
The fix is in discovery only. check-adr-0087-registration.mjs already names
`.changeset` in its own source, so the ordinary watch-hint match fires as soon
as the family is discovered; nothing downstream changed, and no list was added.
Whole-line comments are stripped from bodies so prose about a gate cannot be
mistaken for an invocation of it.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018WuTtyckQa1VcXwgd52JpN
@vercel

vercelBot commented Aug 13, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
ProjectDeploymentActionsUpdated (UTC)
objectstackIgnoredIgnoredAug 13, 2026 2:18pm

Request Review

@hotlonghotlong added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Aug 13, 2026 — with Claude
@hotlonghotlong removed the size/m label Aug 13, 2026 — with Claude
@hotlong
hotlong marked this pull request as ready for review August 13, 2026 14:27
@hotlong
hotlong enabled auto-merge August 13, 2026 14:28
@hotlong
hotlong disabled auto-merge August 13, 2026 14:29
@hotlong
hotlong enabled auto-merge August 13, 2026 14:29
@hotlong
hotlong disabled auto-merge August 13, 2026 14:29
@hotlong
hotlong enabled auto-merge August 13, 2026 14:30
@hotlong
hotlong added this pull request to the merge queueAug 13, 2026
Merged via the queue into main with commit 03b5f81Aug 13, 2026
29 of 32 checks passed
@hotlong
hotlong deleted the claude/issue-8410-gates-changeset-derivation branch August 13, 2026 14:55
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

skip-changesetPR has no user-facing published change; bypasses the changeset gate

Projects

None yet

2 participants

@hotlong@claude