Skip to content

--diff-filter=AM lets a RENAMED changeset carry a violation past check-empty-changeset and check-adr-0087-registration #7045

Description

@os-project-manager

Found while diff-scoping check-changeset-no-major.mjs (#7005). Not fixed there — that PR closed the hole in its own file only, because each sibling's fixtures and messages are its own. Filed per Prime Directive #10. Unclaimed.

The observation

Both scripts/check-empty-changeset.mjs:229 and scripts/check-adr-0087-registration.mjs:1056 enumerate the diff with:

git diff --name-status --diff-filter=AM <from> <head> -- '.changeset/*.md'

Git's rename detection is on by default (diff.renames, since git 2.9), so a changeset that is renamed and modified in the same commit is reported with status R, which --diff-filter=AM drops. Neither gate sees the file at all.

Measured

git 2.43.0, temp repo, one changeset renamed and its frontmatter changed in one commit:

--- default (rename detection on) ---
R075	.changeset/old.md	.changeset/new.md

--- with --diff-filter=AM ---
(empty)

Same fixture shape, run against check-changeset-no-major.mjs before its own fix: 0 violations where the correct answer is 1.

The consequence in each gate

Unlike #7005 this one is live today: both gates run on every PR right now, neither is behind a pre-mode exemption.

Impact, stated honestly

  • No known instance. Changeset filenames are generated by pnpm changeset and essentially never renamed by hand, so this is not something an author trips over by accident — it is a bypass that requires doing an unusual thing.
  • It is not dormant drift either: the code path runs on every PR, and the bypass is one git mv away for anyone who wants it.
  • Both gates are advisory-shaped (they protect the release train, not a security boundary), which is the honest reason this is filed rather than hot-fixed.

The fix, already written once

#7005 / PR #7048 closes the same hole in the third member of the family, and the change is small enough to quote in full:

  1. --diff-filter=AMR instead of AM;
  2. R is R<score>\t<old path>\t<new path>, so the head path is field 3 and the branch-point path is field 2 — read the base side at the pre-rename name;
  3. everything else is unchanged, and a pure rename then compares equal and stays exempt (no false red for moving a stock file).

Both siblings already compare head-vs-base per file, so step 2 is the only real work. The fixture wants a control asserting git really reported R — a body long enough to score as a rename, otherwise the case silently degrades to add-plus-delete and the test passes for the wrong reason.

Related: #7005 (the same hole in the third parser, fixed), #7004 (the family's shared entry regex), #6129 (why all three are diff-scoped at all).

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