Found by the independent verifier on #2341 (full verdict, 17 fixtures, at pull/2341#issuecomment-5263559579). Filed separately from #2342 because that issue tracks deliberate scope gaps; this is a correctness regression in what #2341 shipped, live on a required gate.
To be clear up front: #2341 is a net improvement and not a revert candidate. It killed the two false positives it targeted, plus an unnamed third on trailing whitespace, and it does not go soft on genuine absorption. This is narrower than what it fixed.
The defect
Routing preservation through changelog_versions made inline-linked headings invisible:
## [1.0.0](https://github.com/o/r/releases/tag/v1.0.0) - 2026-01-01
changelog_versions' trailing anchor is \]?([[:space:]]|$), which does not match when ( immediately follows ]. #2290's ^## \[<ver>\] prefix match did see this form.
1. False negative — a regression vs the pre-fix script
Fork has a linked heading; head absorbs it entirely.
shipped bb817c6c : EXIT=0 <- absorption PASSES
pre-fix bb817c6c^: EXIT=1 <- absorption caught
So for this heading shape, main today is worse than before #2341. Every fixture was run against the pre-fix script as a positive control, so EXIT=0 is distinguishable from "the fixture never reached the check".
2. False positive — on the same gate that accepts the form
Plain ## [1.0.0] reformatted to the linked form, release preserved:
shipped: EXIT=1 ABSORBED CHANGELOG HEADING
And --check-bump accepts the linked form for the bumped version (EXIT=0, no FORMAT failure). So the script simultaneously accepts linked headings and red-lines you for producing one. A PR adding compare links to an old changelog heading is blocked today.
3. Two pre-existing blind spots the same anchor causes — NOT #2341's fault
Recorded because they share a root cause and a fix:
--check passes a linked heading naming a version above its manifest, where the plain control gives EXIT=1 CHANGELOG AHEAD OF MANIFEST.
--check-order passes a misordered linked heading, where the plain control gives EXIT=1 MISORDERED.
The blind spot, for the third time
The corpus contains zero linked headings. This is the same failure that let #2290 ship: a sweep proves a check is quiet on the corpus, not correct on the format. #2341's own verification was rigorous — 100 commits replayed, 10,233 fork-point headings compared, 0 fires — and could not have caught this, because the shape does not occur here.
Inline-linked headings are a widespread Keep a Changelog convention. They belong in the fixtures whether or not this repo uses them today, exactly as [YANKED] and dated forms did.
Suggested fix
Relax changelog_versions' trailing anchor to accept ( after the closing bracket, rather than giving preservation its own extractor.
The verifier argues the two are not equivalent, and I agree: a separate extractor re-creates the duplicate reader #2341 deliberately deleted, and leaves --check / --check-order blind to the same form. Fixing the shared anchor closes all three at once.
Add fixtures for: linked heading absorbed (must fail), plain→linked reformat (must pass), linked heading with and without a trailing date, and the existing [YANKED] / dated / trailing-whitespace cases so the set covers the documented format rather than the local corpus.
Verification scope, carried from the verdict
Not examined: #2341's 90 new test lines (the verifier's fixtures were independent of them); shellcheck/portability gates; CI wiring; #2342 item 3's fail-open 2>/dev/null fork-point read; duplicate-version fork headings (presence-keying means deleting one of two identical ## [1.0.0] sections passes, though --check-order already rejects duplicates).
Platform delta: all runs on Cygwin bash 5.3.9 with GNU grep/awk/comm; CI is Linux. Both findings above are regex-level and platform-independent. A separate cross-plugin non-finding (head_documents being declare -A'd outside the loop — verified clean in both glob orderings) is the one worth re-confirming on the CI image.
Found by the independent verifier on #2341 (full verdict, 17 fixtures, at pull/2341#issuecomment-5263559579). Filed separately from #2342 because that issue tracks deliberate scope gaps; this is a correctness regression in what #2341 shipped, live on a required gate.
To be clear up front: #2341 is a net improvement and not a revert candidate. It killed the two false positives it targeted, plus an unnamed third on trailing whitespace, and it does not go soft on genuine absorption. This is narrower than what it fixed.
The defect
Routing preservation through
changelog_versionsmade inline-linked headings invisible:changelog_versions' trailing anchor is\]?([[:space:]]|$), which does not match when(immediately follows]. #2290's^## \[<ver>\]prefix match did see this form.1. False negative — a regression vs the pre-fix script
Fork has a linked heading; head absorbs it entirely.
So for this heading shape,
maintoday is worse than before #2341. Every fixture was run against the pre-fix script as a positive control, soEXIT=0is distinguishable from "the fixture never reached the check".2. False positive — on the same gate that accepts the form
Plain
## [1.0.0]reformatted to the linked form, release preserved:And
--check-bumpaccepts the linked form for the bumped version (EXIT=0, no FORMAT failure). So the script simultaneously accepts linked headings and red-lines you for producing one. A PR adding compare links to an old changelog heading is blocked today.3. Two pre-existing blind spots the same anchor causes — NOT #2341's fault
Recorded because they share a root cause and a fix:
--checkpasses a linked heading naming a version above its manifest, where the plain control givesEXIT=1 CHANGELOG AHEAD OF MANIFEST.--check-orderpasses a misordered linked heading, where the plain control givesEXIT=1 MISORDERED.The blind spot, for the third time
The corpus contains zero linked headings. This is the same failure that let #2290 ship: a sweep proves a check is quiet on the corpus, not correct on the format. #2341's own verification was rigorous — 100 commits replayed, 10,233 fork-point headings compared, 0 fires — and could not have caught this, because the shape does not occur here.
Inline-linked headings are a widespread Keep a Changelog convention. They belong in the fixtures whether or not this repo uses them today, exactly as
[YANKED]and dated forms did.Suggested fix
Relax
changelog_versions' trailing anchor to accept(after the closing bracket, rather than giving preservation its own extractor.The verifier argues the two are not equivalent, and I agree: a separate extractor re-creates the duplicate reader #2341 deliberately deleted, and leaves
--check/--check-orderblind to the same form. Fixing the shared anchor closes all three at once.Add fixtures for: linked heading absorbed (must fail), plain→linked reformat (must pass), linked heading with and without a trailing date, and the existing
[YANKED]/ dated / trailing-whitespace cases so the set covers the documented format rather than the local corpus.Verification scope, carried from the verdict
Not examined: #2341's 90 new test lines (the verifier's fixtures were independent of them); shellcheck/portability gates; CI wiring; #2342 item 3's fail-open
2>/dev/nullfork-point read; duplicate-version fork headings (presence-keying means deleting one of two identical## [1.0.0]sections passes, though--check-orderalready rejects duplicates).Platform delta: all runs on Cygwin bash 5.3.9 with GNU grep/awk/comm; CI is Linux. Both findings above are regex-level and platform-independent. A separate cross-plugin non-finding (
head_documentsbeingdeclare -A'd outside the loop — verified clean in both glob orderings) is the one worth re-confirming on the CI image.