Skip to content

Release pipeline silently ships releases with no CHANGELOG section (4 so far), and races the merge window #1477

Description

@paul-hammant

The release pipeline's CHANGELOG handling has two failure modes, both silent, and both have now bitten. Neither is a bug in the code as written — release.yml:173-176 does exactly what it says — but the surrounding conditions let work ship unrecorded or misattributed.

Failure mode 1: no [current] → the release records nothing

if grep -q '## \[current\]' CHANGELOG.md; then
  sed -i "s/## \[current\]/## [${VERSION}]/" CHANGELOG.md
  git add CHANGELOG.md
fi

When a merge adds no [current] section, the if is simply false and the release proceeds with no CHANGELOG section at all — no warning, no failure.

Observed: releases 0.506.0 through 0.509.0 all shipped with no section. VERSION read 0.509.0 while the newest heading was [0.505.0]. Four releases of real work — the Windows .exe install fix, the nightly install/tags fix, the fs.read error-detail work, contrib/avcodec — recorded nowhere. Backfilled in #1474.

Failure mode 2: the rename races the merge window

The release workflow branches from main when it starts. A feature PR that merges between that moment and the release merge is in the release but not in its CHANGELOG.

Observed, 18 seconds apart:

20:02:45  #1476 merged to main       (feat: io.fd_read_into)
20:03:03  #1475 merged               (chore: release 0.510.0)

The release branch was cut before #1476 landed, so grep -q '## [current]' correctly found nothing — the previous [current] had just been consumed by the backfill and the new entry did not yet exist. Net effect: 0.510.0 shipped fd_read_into while its entry sat under [current], about to be misattributed to 0.511.0. Corrected by hand.

Failure mode 3 (older, same family): the rename can create a duplicate

## [0.435.0] and ## [0.497.0] each appear twice on main today. A blind sed rename will happily create a heading that already exists, which is how those arose.

Suggested fix

Three guards, cheap and independent:

  1. Fail the release when there is no [current] to rename. A release with nothing to say is nearly always an oversight, not an intent. If a genuinely empty release is wanted, make that explicit — an [skip changelog] token in the release-trigger commit, or a hand-written empty section.

  2. Refuse to create a duplicate heading. Before the sed, check whether ## [${VERSION}] already exists and fail loudly if so.

  3. Close the race. Either re-check for [current] immediately before the release PR merges (rather than only when the branch is cut), or have the release workflow rebase onto main and re-run the rename as a final step. A cheaper mitigation: a CI check on every non-release PR that fails when the diff touches std/, runtime/, compiler/ or contrib/ but not CHANGELOG.md — that at least guarantees a [current] exists to be renamed.

Guard 1 alone would have caught all four missing releases. Guard 3's CI check would have prevented them being missing in the first place.

Why this matters more than tidiness

The CHANGELOG is what a downstream consumer reads to decide whether to upgrade. Four consecutive releases of silence — including a Windows packaging fix that made installs unusable — is the kind of gap that erodes trust in the file entirely, at which point people stop reading it and stop maintaining it.

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