You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The beta release flow builds CHANGELOG.md by running git merge beta/main over it (release skill, step 5). Every release section ends with the same boilerplate — the shared genuinely new since then. note followed by ### Fixed — so the newly inserted section's trailing context matches the previous section's existing lines, and git's 3-way merge collapses the insertion into its predecessor.
It is silent, and it has already fired three times:
section
entries at its own tag
entries on beta/main
b7
14
19
absorbed b8's
b8
0
0
its 6 entries landed under b7
b9
7
12
absorbed b10's
b10
0
0
its 5 entries landed under b9
b8 and b10 shipped to users with an empty changelog section. The b8 merge additionally dropped b7's #512 Finer battery optimization grid line outright. b1–b6 are clean. It fired a fourth time during the v10.1.0b11 release and was caught and hand-repaired there (beta PR #103) — that repair restores b8's and b10's sections and b7's dropped line, but does nothing to stop the next occurrence.
Nothing in the flow detects any of this. Step 4's curation rules ("drop what already shipped", "detect PRs with no entry") are prose executed by judgment and verified by nothing, and the published history on beta/main — which the next release reads as its source of truth — is exactly the thing being corrupted. A release that trusts it inherits the damage and compounds it.
Fix
1. Stop merging the file. A beta release only ever prepends one section. Resolve CHANGELOG.md deterministically — take beta/main's copy verbatim and insert the new section programmatically — instead of letting a 3-way merge guess. This removes the failure mode rather than detecting it.
2. Add scripts/check-changelog.py, asserting three invariants:
Prepend-only — strip the new section from the release branch's CHANGELOG.md; the remainder must be byte-identical to git show beta/main:CHANGELOG.md. Any edit to already-published history, anywhere in the file, fails. This catches the whole class (absorbed sections, dropped lines, silent reflows) without knowing what any section should say. This is the check that makes maintainer review of the changelog unnecessary.
Coverage — every (#N) merge commit on origin/main since the previous beta tag must either appear in the new section or be named on an explicit "internal, no entry" list that the script prints. A missed user-facing PR cannot pass silently; it has to be dismissed on the record.
No re-announcing — every entry in the new section must reference a PR that merged after the previous beta's cut point. This is the curation rule step 4 describes and nothing enforces.
3. Wire it into the release skill — steps 4 and 5 call the script instead of describing the procedure in prose.
Notes
A one-shot independent audit (each section vs. its content at its own tag) is what surfaced this. It stays useful, but needs an allowlist for the two sections repaired in beta PR Growatt integration stoped working #103. The prepend-only invariant needs no allowlist and prevents recurrence outright, so that is the one to wire into the flow.
Scope is the beta flow's changelog handling plus the release skill. No product code.
origin/main's Unreleased section was never affected — all the content is intact there. The corruption is confined to published beta history.
Problem
The beta release flow builds
CHANGELOG.mdby runninggit merge beta/mainover it (release skill, step 5). Every release section ends with the same boilerplate — the sharedgenuinely new since then.note followed by### Fixed— so the newly inserted section's trailing context matches the previous section's existing lines, and git's 3-way merge collapses the insertion into its predecessor.It is silent, and it has already fired three times:
beta/mainb8 and b10 shipped to users with an empty changelog section. The b8 merge additionally dropped b7's
#512 Finer battery optimization gridline outright. b1–b6 are clean. It fired a fourth time during the v10.1.0b11 release and was caught and hand-repaired there (beta PR #103) — that repair restores b8's and b10's sections and b7's dropped line, but does nothing to stop the next occurrence.Nothing in the flow detects any of this. Step 4's curation rules ("drop what already shipped", "detect PRs with no entry") are prose executed by judgment and verified by nothing, and the published history on
beta/main— which the next release reads as its source of truth — is exactly the thing being corrupted. A release that trusts it inherits the damage and compounds it.Fix
1. Stop merging the file. A beta release only ever prepends one section. Resolve
CHANGELOG.mddeterministically — takebeta/main's copy verbatim and insert the new section programmatically — instead of letting a 3-way merge guess. This removes the failure mode rather than detecting it.2. Add
scripts/check-changelog.py, asserting three invariants:CHANGELOG.md; the remainder must be byte-identical togit show beta/main:CHANGELOG.md. Any edit to already-published history, anywhere in the file, fails. This catches the whole class (absorbed sections, dropped lines, silent reflows) without knowing what any section should say. This is the check that makes maintainer review of the changelog unnecessary.(#N)merge commit onorigin/mainsince the previous beta tag must either appear in the new section or be named on an explicit "internal, no entry" list that the script prints. A missed user-facing PR cannot pass silently; it has to be dismissed on the record.3. Wire it into the release skill — steps 4 and 5 call the script instead of describing the procedure in prose.
Notes
origin/main'sUnreleasedsection was never affected — all the content is intact there. The corruption is confined to published beta history.