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).

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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