Skip to content

fix(release): restore changelog generation, backfill v1.0.0-v1.33.0, detect the gap - #419

Merged
abrichr merged 4 commits into
mainfrom
chore/changelog-gap-repair
Aug 28, 2026
Merged

fix(release): restore changelog generation, backfill v1.0.0-v1.33.0, detect the gap#419
abrichr merged 4 commits into
mainfrom
chore/changelog-gap-repair

Conversation

@abrichr

@abrichr abrichr commented Aug 28, 2026

Copy link
Copy Markdown
Member

What happened

CHANGELOG.md stopped at v0.26.0 (2026-07-14). The package shipped 1.0.0 through 1.33.0 anyway. The 1.34.0 release dispatch hit the workflow's changelog gate and stopped.

The cause is a one-line behaviour change inside a grouped dependency bump.

time (UTC, 2026-07-14) event
17:25:25 chore: release 0.26.0 — the last entry the generator ever wrote
17:44:41 #87 merges, python-semantic-release 9.15.2 → 10.6.1
17:47:20 chore: release 1.0.0 — version bumped, no changelog entry

Version 10 changed the default changelog mode from init to update. In update mode the generator inserts each new version after insertion_flag and writes nothing when that flag is absent — exit 0, no warning, no diff. This file was written entirely under version 9, so it had no flag. Confirmed against the pinned 10.6.1: ChangelogConfig().mode is UPDATE and insertion_flag is <!-- version list -->; running semantic-release changelog on the old file changes nothing and exits 0.

The same bump explains the odd 0.26.0 → 1.0.0 jump 22 minutes later.

Did 30+ releases ship past this gate?

No. The gate is newer than every release it would have caught. It was added on 2026-08-26 (ci: require release App tag publication), one day after v1.33.0. The 1.34.0 dispatch is its first evaluation, so its first run was also the first report of the problem. It worked. It just had nothing to compare against for six weeks.

What this does

Restores generation. Adds the insertion flag, and pins mode and insertion_flag explicitly in pyproject.toml — the regression was caused by depending on a default, so the values are now stated rather than inherited.

Backfills v1.0.0 – v1.33.0 mechanically. Not hand-reconstructed: the entries are the output of the same generator re-run over the same tagged commits, and re-running it reproduces them. A note at the top of the file records that the range was reconstructed, when, and why.

A blanket mode = "init" regeneration was rejected. Version 10's default template emits the commit summary line only, so regenerating the whole file would have replaced the 30 detailed v0.x entries with one-liners and discarded their commit bodies. The backfill is spliced in above them instead. All 30 v0.x entries are byte-identical to what was committed at the time; the only other change to that region is blank-line normalisation.

Adds the v1.34.0 entry, which unblocks the release. Its content is generated from the real commits, and covers more than the summary that prompted this work: #399, #400, #401, #404, #405, #406, #407, #408, #409, #410, #411, #414, #415, #416, #417 and the three release-contract CI commits.

Marks v1.13.0 and v1.14.0 as yanked, verified against the PyPI JSON API.

What would have caught it sooner

A comparison of published tags against changelog headings. It would have alerted on 2026-07-14, on the next scheduled run after v1.0.0 shipped.

release-health.yml already exists for exactly this class of gap — a scheduled, look-only detector for things that go wrong quietly — so this adds a fourth detector to it rather than a new workflow. No ci.yml change, so no wheel-hash re-pin.

It is state-based like its siblings: the alarm is "a published version is undocumented", not "a generator run did something unexpected". That matters because this repository has already moved once from generator-written entries to entries prepared in a reviewed PR, and the detector has to survive that. It reuses the lane's publish grace window, warns rather than alerts when the file is unreadable, stays silent for a lane with no changelog_file, and matches the same heading shape the release workflow greps for so the gate and the detector cannot disagree. Tag dates come from the releases already fetched rather than one call per tag.

Verification

  • Detector run live against this repository: 52 undocumented versions against the pre-fix changelog, clean against the restored one.
  • Simulated a v1.35.0 release against the committed tree: inserts correctly below the flag, note intact, v1.34.0 intact, all 107 v0.x commit bodies intact.
  • That simulation caught a real bug in a first draft — the note quoted the insertion flag literally, so the generator inserted into the note and split it. Fixed; the literal appears exactly once.
  • Self-test extended with five cases including a replay of the actual 2026-07-14 state. Full suite passes.
  • check_release_consistency.py passes; the artifact-hash gate flagged the config edit and the regenerated inventory is a single reviewed hash change.
  • ruff check / ruff format --check clean on the changed file. The 9 pre-existing scripts/ lint errors are unchanged from main and outside CI's lint scope (openadapt_flow only).

The changelog gate is untouched.

🤖 Generated with Claude Code

abrichr and others added 2 commits August 27, 2026 23:28
python-semantic-release 9.15.2 -> 10.6.1 landed on 2026-07-14 in a grouped
dependency bump (#87). Version 10 changed the default changelog mode from
"init" to "update". In "update" mode the generator inserts each new version
after `insertion_flag` and writes nothing at all when that flag is absent: it
exits 0, emits no warning, and leaves no diff. This file had been written
entirely under version 9, so it carried no flag.

The timing is exact. v0.26.0 was released at 17:25 UTC that day and is the last
entry the generator ever wrote. The bump merged at 17:44. v1.0.0 was released
at 17:47 with no entry, and every release through v1.33.0 did the same: six
weeks, 34 published versions, no changelog and no signal.

Restore generation by adding the insertion flag, and pin `mode` and
`insertion_flag` explicitly so the next major bump of the generator cannot
change them silently. The regression was caused by depending on a default.

Backfill v1.0.0 through v1.33.0 by re-running that same generator over the same
tagged commits. Nothing here is hand-written; the entries are the tool's own
output and re-running it reproduces them. A note at the top of the file records
that the range was reconstructed, on what date, and why, and states that
version 10's default template emits the commit summary line only, so the
reconstructed entries carry no commit bodies. `mode = "init"` would have
regenerated the whole file under that same template and discarded the bodies in
the v0.x entries, so the backfill was spliced in above them instead: all 30
v0.x entries are byte-identical to what was committed at the time.

Mark v1.13.0 and v1.14.0 as yanked on PyPI.

Add the v1.34.0 entry, which the release workflow's changelog gate requires.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…g entry

The changelog stopped being written on 2026-07-14 and nothing reported it for
six weeks. The release workflow does gate on a changelog entry, but that gate
was added on 2026-08-26, after all 34 of the affected releases, so its first
evaluation was also the first report of the problem, and it surfaced as a
blocked release rather than as a finding.

Comparing published tags against changelog headings would have alerted on
2026-07-14, on the next scheduled run after v1.0.0 shipped. This adds that
comparison as a fourth detector in the lane that already watches for unreleased
work and incomplete publishes.

It is state-based like its siblings: the alarm is "a published version is
undocumented", not "a generator run did something unexpected". That distinction
matters here because this repository has already moved once from
generator-written entries to entries prepared in a reviewed pull request, and
the detector has to survive that kind of change. It reuses the lane's existing
publish grace window so a tag minted moments ago is not called undocumented,
warns rather than alerts when the file cannot be read, stays silent for a lane
that declares no `changelog_file`, and matches the same heading shape the
release workflow greps for so the gate and the detector cannot disagree.

Tag dates come from the releases already fetched rather than one API call per
tag: a six-week gap is 34 tags, and the detector must not spend 34 requests to
notice it.

Verified in both directions against the live repository: it reports 52
undocumented versions against the pre-fix changelog and reports clean against
the restored one.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@abrichr
abrichr enabled auto-merge (squash) August 28, 2026 03:30
abrichr and others added 2 commits August 28, 2026 00:04
`feat(sanitize): materialize an approved derivative from its archive` merged to
main while this branch was in review. It is unreleased, so it belongs in
v1.34.0. Regenerated the entry from the merged history rather than appending by
hand, so it stays exactly what the generator produces.

Date the entry 2026-08-28, the day it lands.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@abrichr
abrichr merged commit d139db9 into main Aug 28, 2026
20 checks passed
@abrichr
abrichr deleted the chore/changelog-gap-repair branch August 28, 2026 04:43
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.

1 participant