ci: gate changelogs on version order and duplicate versions - #1762
Conversation
The 3.1.1 regression this branch renumbers reached main because nothing read the changelog as a SEQUENCE. Both existing modes reason about one version at a time: --check asks whether a versioned plugin has a changelog at all, and --check-bump asks whether this change set added an entry for its own new version. Neither can see that a branch staged a number already behind main, or that two branches staged the same one — and a reviewer sees only their own diff hunk, never the resulting order. --check-order reads each changelog whole and fails on a version that sits below a later one, or on any version listed twice. It covers docs/conventions/* changelogs too, which is where this shipped: those carry no manifest version, so the other two modes never look at them at all. Comparison is `sort -rV`, so 10.0.0 correctly outranks 9.0.0, and both heading forms in this repo are parsed — bracketed `## [1.2.3]` for plugins and `## 1.2.3 — date` for conventions. Verified adversarially: the gate fails on main's current loop-lane changelog and passes once this branch's renumber is applied. Tests: 26 -> 32 cases, 0 failures. Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Claude finished @kyle-sexton's task in 2m 40s —— View job Code Review
Overall this is a clean, well-motivated addition. The version-ordering logic is correct and the CI wiring is intentional. I found one formatting bug in the error message, a latent design coupling, and a minor test gap. Bug — missing space in duplicate error message
echo "DUPLICATE CHANGELOG VERSION: $changelog lists $(printf '%s' "$dupes" | tr '\n' ' ')more than once. ..."
The space before Fix: add a literal space before echo "DUPLICATE CHANGELOG VERSION: $changelog lists $(printf '%s' "$dupes" | tr '\n' ' ') more than once. ..."Design coupling — manifest check gates
|
|
Claude finished @kyle-sexton's task in 2m 13s —— View job Security Review — PR #1762
No security issues found. The review covered the four changed files through the lens of injection surfaces, logic/trust-boundary flaws, and dangerous GitHub Actions patterns. Findings per surface:
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 95c2cab1bc
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
shell-portability-lint bans `sort -V` as a GNU extension, and the comment claiming the lint permitted it was simply wrong — an unverified assertion that CI caught. Replaced with a zero-padded sort key, so a plain lexical comparison orders versions numerically and 10.0.0 still outranks 9.0.0. Five digits per field is far past anything this repo will reach, and the extraction regex admits only digits, so no field can overflow the padding silently. `10#` forces base 10 so a zero-padded field is never read as octal. No suppression added: the construct is resolved, not exempted. Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Claude finished @kyle-sexton's task in 2m 6s —— View job Review (updated commit —
|
|
Claude finished @kyle-sexton's task in 2m 12s —— View job Security Review — PR #1762
No security issues found. The review covered the four changed files, focusing on logic flaws, authorization gaps, injection surfaces, unsafe data handling, and dangerous GitHub Actions patterns that static analysis cannot reach. Surfaces examined and cleared:
|
The extraction regex required a patch component, so --check-order matched NOTHING in the five convention changelogs whose headings are `## 1.2` — config-cascade, finding-suppression, hook-config-delivery, hook-telemetry, and permission-rule-hygiene — and reported success for them. That is worse than not covering those files: a gate that silently matches nothing is indistinguishable from a gate that passed, which is the same failure shape this gate exists to catch. The regex now accepts `major.minor` as well as `major.minor.patch`, and version_sort_key already zero-fills the absent field, so mixed widths compare correctly in one file (1.10 outranks 1.9, and 1.9.1 sits between them). Tests: 32 -> 35 cases, 0 failures. Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Claude finished @kyle-sexton's task in 1m 55s —— View job Review (updated commit —
|
|
Claude finished @kyle-sexton's task in 1m 36s —— View job Security Review — PR #1762 (updated commit
|
Why
#1758 renumbered a
3.1.1entry that had reachedmainsitting below4.0.0. This gate is thereason that could happen at all, and it closes it.
The entry was authored against
3.1.0, and merged (#1686,17:46:59Z) after4.0.0had alreadylanded (
17:44:23Z). Its number was a regression the instant it merged. Nothing caught it, becauseno gate reads a changelog as a sequence:
--checkasks whether a versioned plugin has a changelog at all.--check-bumpasks whether this change set added an entry for its own new version.Both reason about one version in isolation, so neither can see that a branch staged a number already
behind
main, or that two branches staged the same one. A reviewer cannot see it either — the diffhunk shows the new entry, never the resulting order.
That is not a one-off. The batch this came from had
source-control 0.34.0claimed by four branchesand
work-items 0.26.0by five; those were caught only because a human renumbered them by hand,one merge at a time.
What this adds
scripts/check-changelog-parity.sh --check-orderreads each changelog whole and fails on:Wired into
ci.ymlas a non-PR-scoped step, because the defect is a property of the merged filerather than of any one diff.
Scope note
It covers
docs/conventions/*/CHANGELOG.mdas well asplugins/*/CHANGELOG.md. That is deliberateand load-bearing: convention changelogs carry no manifest version, so the other two modes never look
at them — and a convention changelog is exactly where this shipped.
Verification
Adversarial, not just green: the gate fails on
main's pre-#1758 loop-lane changelog and passesonce the renumber is applied.
check-changelog-parity.test.sh: 26 → 32 cases, 0 failures. Includes the exact shape thatshipped (
6.0.0 → 3.1.1 → 5.0.0 → 4.0.0, unbracketed convention headings), a duplicate-versioncase, and a
10.0.0 > 9.0.0case so the comparison cannot regress to lexical.shellcheck -xon both scripts — clean, no suppressions added.--check-orderacross the repo: all 71 changelogs pass.Both heading forms this repo uses are parsed:
## [1.2.3](plugins) and## 1.2.3 — date(conventions). Comparison is
sort -rV.Related
No linked issue — this is the preventive half of #1758, which fixed the instance.
Refs #1758
Refs #1686