Skip to content

fix(ci): baseline the release changelog on the previous tag, not published_at - #125

Open
basilebong wants to merge 1 commit into
mainfrom
fix/release-changelog-baseline
Open

fix(ci): baseline the release changelog on the previous tag, not published_at#125
basilebong wants to merge 1 commit into
mainfrom
fix/release-changelog-baseline

Conversation

@basilebong

Copy link
Copy Markdown
Collaborator

The bug

publish.yml's release job calls releases/generate-notes without previous_tag_name, leaving GitHub to pick the baseline: the newest prior release by published_at. That ordering is permanently wrong in this repo.

alpha.3's Release was backfilled by hand on 2026-07-21, two seconds after alpha.4's was created:

tag published_at
v0.0.1-alpha.4 2026-07-21T07:42:45Z
v0.0.1-alpha.3 2026-07-21T07:42:47Z ← later

So alpha.3 looks like the most recent release forever, and every changelog baselines against it.

This already shipped. v0.0.1-alpha.5's auto-generated body spanned alpha.3...alpha.5 and re-listed five PRs already released in alpha.4 — #105, #103, #107, #106, #108. I corrected that release's body by hand. Without this fix, alpha.6 and every release after it reproduce it, and the duplication grows.

published_at isn't settable via the API, so the mis-ordering can't be repaired at the source.

The fix

Resolve the previous tag from git ancestry — a backfilled Release can't reorder the commit graph:

PREV="$(git describe --tags --abbrev=0 --match 'v*.*.*' "$TAG^" 2>/dev/null || true)"

Empty PREV (the very first tag, which has no predecessor) omits the field and lets generate-notes fall back to its own default.

This needs a checkout in the release job, with fetch-depth: 0 because git describe walks history and a shallow clone can't see prior tags. persist-credentials: false per supply-chain.md; the job keeps its single contents: write grant, so least-privilege is unchanged.

Why not git tag -l --sort=-v:refname

It's the obvious alternative and it's a trap — it sorts prereleases above the stable release they precede unless versionsort.suffix is configured:

$ git tag -l 'v0.1.0*' --sort=-v:refname
v0.1.0-alpha.1     ← wrong, should be below
v0.1.0

It happens to work on today's all-prerelease tag list, then breaks on the first stable release — the one release you least want wrong. Ancestry needs no config and has no such edge.

Verification

No test accompanies this: the repo has no harness for workflow YAML, and a CI-only path can't be exercised without a tag push. Verified by hand instead:

  • YAML parses; the release job's steps and permissions are as intended.
  • The extracted run script passes bash -n and shellcheck (the one remaining SC2016 is pre-existing and a false positive — those %s are printf placeholders).
  • The resolution logic dry-runs correctly over the real tags:
    v0.0.1-alpha.5 -> prev='v0.0.1-alpha.4'
    v0.0.1-alpha.4 -> prev='v0.0.1-alpha.3'
    v0.0.1-alpha.2 -> prev='<none>'        # first tag, field omitted
    
  • generate-notes with previous_tag_name=v0.0.1-alpha.4 returns exactly the 10 commits in alpha.4..HEAD.

The real proof is alpha.6's changelog starting at alpha.5. Worth a glance at the "Full Changelog" compare link on the next release.

🤖 Generated with Claude Code

…ished_at

`publish.yml`'s release job called `releases/generate-notes` without
`previous_tag_name`, so GitHub chose the baseline itself — the newest
prior release by `published_at`. That ordering is permanently wrong in
this repo: alpha.3's Release was backfilled on 2026-07-21 two seconds
after alpha.4's was created, so alpha.3 has the later `published_at` and
looks like the most recent release forever.

alpha.5 shipped with the result: a changelog spanning alpha.3...alpha.5
that replayed five PRs already released in alpha.4 (#105, #103, #107,
#106, #108). Its body was corrected by hand after the fact; without this
fix alpha.6 and every release after it reproduce the same bug.

Resolve the previous tag from git ancestry instead — a backfilled
Release can't reorder the commit graph. This needs a checkout in the
release job (`fetch-depth: 0`, since `git describe` walks history);
`persist-credentials: false` per supply-chain.md, and the job keeps its
single `contents: write` grant.

`git tag -l --sort=-v:refname` was the obvious alternative and is a
trap: it sorts `v0.1.0-alpha.1` above `v0.1.0` unless
`versionsort.suffix` is set, so it would break on the first stable
release — the one release you least want wrong. Verified both against
the real tags before choosing.

No test accompanies this: the repo has no harness for workflow YAML, and
a CI-only path can't be exercised without a tag push. Verified by hand
instead — YAML parses, the extracted script passes `bash -n` and
shellcheck, and the resolution logic dry-runs correctly over the real
tags (alpha.5 -> alpha.4, alpha.4 -> alpha.3, alpha.2 -> no predecessor,
which correctly omits the field). The real proof is alpha.6's changelog.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants