Skip to content

A trailing YAML comment hides a bump entry from all three changeset frontmatter parsers #7004

Description

@os-project-manager

Measured while adding --self-test fixtures to scripts/check-changeset-no-major.mjs (#6923). Not fixed there — it is a family-wide change across three scripts with a different consequence in each, so it is out of that PR's fix face. Filed per Prime Directive #10. Unclaimed.

The observation

All three changeset frontmatter parsers share one entry regex:

/^\s*["']?([^"':]+)["']?\s*:\s*([A-Za-z]+)\s*$/

The bump group is anchored ([A-Za-z]+)\s*$, so anything after the bump word defeats the match. A trailing YAML comment is the reachable case.

Measured on origin/main @ d3e53f2d8, by extracting each function's real source text and loading it standalone (no top-level main executed), so this is the shipped code and not a re-implementation:

■ '"@objectstack/spec": major # keep'
    check-empty-changeset.declaredBumpsIn  : {"fenced":true,"packages":[]}   <== judged EMPTY-FRONTMATTER
    check-adr-0087.parseChangeset          : []
    check-changeset-no-major.majorPackagesIn : []
■ control, the same line without the comment
    check-empty-changeset.declaredBumpsIn  : {"fenced":true,"packages":["@objectstack/spec"]}
    check-adr-0087.parseChangeset          : [{"pkg":"@objectstack/spec","bump":"major"}]
    check-changeset-no-major.majorPackagesIn : ["@objectstack/spec"]

And the ground truth it is measured against — @changesets/parse@0.4.3, the version this repo resolves:

trailing comment  => [{"name":"@objectstack/spec","type":"major"}]

So changesets reads it as a real major. All three gates read it as nothing.

Why it is three different bugs, not one

script what it misses direction
check-changeset-no-major.mjs the major itself false GREEN — a whole-stack major promoted past a guard that printed a tick
check-empty-changeset.mjs the whole declaration ⇒ the file reads as empty-frontmatter false RED — a PR that added a perfectly valid changeset is rejected under #5471
check-adr-0087-registration.mjs signal (1) major in breakingDeclaration partial — signals (2) **BREAKING and (3) the ! summary can still carry it

The false-RED one is the likelier to be hit first (an author writes a comment, the gate tells them their changeset is empty), and the false-GREEN one is the one that matters, because it is silent and it is aimed at the release train.

Extra force behind the "keep them identical" argument

All three functions carry a comment saying the three parsers deliberately read the same block and must agree. That claim is now true for leading blank lines (PR for #6923 aligned the third one) and it is true here too — they agree by all being wrong together. A family-wide fix keeps the claim true; fixing one alone would make it false again, which is why #6923 did not.

Impact, stated honestly

Candidate dispositions (no conclusion presumed)

  1. Strip a trailing # comment before matching, in all three parsers, in one PR — plus the fixture in each. check-changeset-no-major.mjs already has a pinned assertion for the current behaviour that is written to be flipped rather than deleted when this lands.
  2. Tighten instead of loosen: reject a frontmatter line that is entry-shaped but does not fully match, so an unparseable line is loud rather than invisible. Larger blast radius — worth measuring against the stock first.
  3. Rule the shape out of bounds for authors and say so in the prescription. Cheapest, but it is a convention with no enforcement, which is the class of thing this repo keeps converting into gates.

Leaning 1. Left to triage.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions