Skip to content

changelog: 22 version headings render as literal brackets, and [Unreleased] compares from v0.2.0 #2324

Closed

Description

@HugoXK

Implementation: FlyBot (ours) — PR follows within 24h. Do not pick up without a pickup-ok comment on this issue.

Written-by: FlyBot Lead
Claimed-by: FlyBot Lead
Claim-Id: 9dcd91ec543043758b98159ce2cb286e
Branch: dev/3-changelog-links
Base: e40cecf
At: 2026-11-04T01:06:31Z

Implementation: FlyBot (ours) — PR follows within 24h. Do not pick up without a pickup-ok comment.

Written-by: FlyBot Lead

What is wrong

CHANGELOG.md renders 22 of its 31 version headings as literal brackets. They are written ## [0.2.22] - 2026-11-04, which Markdown reads as a reference link, but only 8 link definitions exist at the bottom of the file. Every release from 0.2.1 through 0.2.22 has none, so the published changelog shows [0.2.22] — brackets and no link — instead of a compare view. [Unreleased] still points at compare/v0.2.0...HEAD, 22 releases stale.

Measured on live main e40cecf6f5a5b0d19c95797792e98c98bf6382f5:

  • headings ## [x.y.z]: 31 (Unreleased + 0.2.22 … 0.1.0)
  • link definitions present: 8 → missing 22, exactly 0.2.1 … 0.2.22
  • [Unreleased]: https://github.com/ThinkFlowLab/FlyBot/compare/v0.2.0...HEAD
  • tags v0.2.22 … v0.2.0 and v0.1.0 all exist (24 tags), so every compare URL is constructible

Why it happened

.github/scripts/changelog.mjs:

  • release() (:76) builds the new ## [version] - date section and returns the file with it spliced in. It writes no link definition for that version and does not move [Unreleased], so every release needs a by-hand edit afterwards. 22 releases of by-hand edits are what the file shows.
  • check() (:103-105) asserts only that ## [Unreleased] exists and that the fragments are well formed. A heading with no definition, and an [Unreleased] comparing from a stale tag, both pass.

What to change

  1. release(): append a link definition for the released version (compare/v<previous>...v<version>), point [Unreleased] at compare/v<version>...HEAD, and keep the definition block in the order the file already uses (newest first, Unreleased at the top).
  2. check(): fail when a ## [x.y.z] heading has no matching [x.y.z]: definition, and when [Unreleased] compares from anything other than the newest released version heading. Each message names the heading it is about.
  3. Repair CHANGELOG.md once: add the 22 missing definitions in the existing format and repoint [Unreleased].

Acceptance

  • Fail first, before the data repair: node .github/scripts/changelog.mjs check on today's CHANGELOG.md exits non-zero and names the missing versions and the stale [Unreleased].
  • After the repair on this branch: the same command exits 0.
  • release() unit test: a CHANGELOG whose newest heading is 0.2.22 and whose [Unreleased] compares from v0.2.0, releasing 0.3.0 yields the ## [0.3.0] - date section, a [0.3.0]: …compare/v0.2.22...v0.3.0 definition, and [Unreleased]: …compare/v0.3.0...HEAD.
  • --dry-run still writes nothing; the fragment-deletion and CHANGELOG.md write paths are unchanged.
  • node --test ".github/scripts/*.test.mjs" green, and changelog.mjs check on the repaired tree exits 0.

Changelog

changelog.d/<this issue>-changelog-link-definitions.fixed.md — one line: the changelog's version headings link to their release comparisons again, and [Unreleased] compares from the newest release.

Dedup

Searched issues (open and closed) for changelog gate, "link definition", "Unreleased" compare, compare v0.2 in:body: nothing on this face. Nearest history is a different defect — #103 (conflict-proof the generated CHANGELOG), #152 (v0.2.0 release prep), #916 and #1741 (a fragment missing for one fix / one release). Those are about a missing entry; this is about a heading that never renders as a link.

Scope

.github/scripts/changelog.mjs, .github/scripts/changelog.test.mjs, CHANGELOG.md, changelog.d/. test.yml:151-152 already runs node .github/scripts/changelog.mjs check, so this repair reads as no workflow edit; changelog.mjs is not in policy/gates.json's gateFiles, so this file list on its own carries no Policy-sign: line.

If the implementation turns out to need a file that is a gate file (.github/workflows/test.yml, .github/scripts/enqueue-rules.mjs, .github/scripts/enqueue-guard.mjs, or anything else under policy/), the pull request carries Policy-sign: <comment id> like any gate change. Do not narrow the fix to dodge a signature: say in the PR body which gate file it touches and why.

Activity

  1. HugoXK commented on Nov 4, 2026

    @HugoXK
    ContributorAuthor

    Release

    Written-by: FlyBot Lead
    Released: 9dcd91ec543043758b98159ce2cb286e
    Reason: PR #2329 merged as 98a6aec at 2026-11-04T02:17:29Z
    At: 2026-11-04T02:26:13Z

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

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions