Skip to content

check-changelog-parity: absorbed-heading preservation is scoped to bumped plugins, so a changelog-only change set and every convention changelog are unpoliced #2342

Description

@kyle-sexton

Split out of #2327 so that issue can close with #2341, which fixes only its false positives (items 1 and 2). These are #2327 items 3, 4 and 5 — all pre-existing from #2290, none addressed by #2341.

The absorbed-heading check lives inside --check-bump's loop, so it is gated on bumped_candidate[$name] and on [[ "$head_version" != "$fork_version" ]] || continue. Three consequences, each verified against the shipped script on main, copied unmodified into throwaway repos.

1. A changelog-only change set is unpoliced — and this is where the real one hid

base:  ## [0.51.8], ## [0.51.7]      manifest 0.51.8
head:  ## [0.51.8]                   manifest 0.51.8 (untouched)
       (0.51.7's note folded into 0.51.8, its heading deleted)

--check-bump -> EXIT=0

Not hypothetical. Replaying the 60 most recent main commits touching plugins/*/CHANGELOG.md as their own PRs produced zero ABSORBED CHANGELOG HEADING fires — including on 04822fc4, which really did delete ## [0.9.7] from plugins/docs-hygiene/CHANGELOG.md and fold its bullet into ## [0.10.0]. It is invisible because it changed no manifest at all:

$ git diff --name-only 04822fc4^1 04822fc4
.github/workflows/ci.yml
plugins/docs-hygiene/CHANGELOG.md
scripts/check-changelog-parity.sh
scripts/check-changelog-parity.test.sh

"Fix a release note" is the ordinary shape of a changelog-only PR, and it is exactly the shape a bad merge-forward resolution takes.

2. Convention changelogs are unpoliced

docs/conventions/*/CHANGELOG.md are unversioned by any manifest, so --check-bump never reads them — which is precisely why --check-order was given its own whole-file scope (the misordering that produced it shipped in docs/conventions/loop-lane/CHANGELOG.md).

docs/conventions/demo/CHANGELOG.md
base:  ## 2.0.0 — 2026-01-02, ## 1.0.0 — 2026-01-01
head:  ## 3.0.0 — 2026-01-03, ## 1.0.0 — 2026-01-01     <- 2.0.0 absorbed

--check-bump -> EXIT=0

3. The fork-point read is fail-open

git show "$merge_base:$changelog" 2>/dev/null | changelog_versions - inside a process substitution: a git read that genuinely fails yields an empty fork-point set, which reads as "nothing to preserve" and passes. git ls-tree distinguishes the two cases exactly — a path missing from the tree is exit 0 with empty output, an unusable rev is a non-zero exit — where git cat-file -e cannot (it exits 128 for both, so probing with it would fail every change set that adds a changelog).

Shape of a fix

Preservation as its own diff mode over every changed changelog under both roots --check-order sweeps, wired as an additional PR-only step in the existing changelog-parity-gate job so no required-context name changes. It must replace the in-loop check rather than sit beside it — two overlapping absorption checks is the drift the file's own header warns against.

A worked implementation exists on branch fix/2264-changelog-preservation (PR #2298, closed as a duplicate of #2290 before it could land, and not reopenable): 21 tests including [YANKED], heading reformat, whole-file deletion, convention-changelog absorption, a stale-branch false-positive guard, and a forced-gawk SIGPIPE fixture. Swept over 60 replayed main commits, 7789 headings compared, firing on 04822fc4 and nowhere else. It needs rebasing onto #2290 and #2341.

Related

#2264 (the defect), #2290 (the fix that shipped), #2327 (the false positives), #2341 (their repair).

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

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions