Summary
When loaf release has to generate the new changelog section itself (no ## [Unreleased] section present in CHANGELOG.md), the new ## [X.Y.Z] section is appended at the bottom of the file instead of the newest-first position under the header. On levifig/mvault this was manually relocated after four separate releases.
Narrowing evidence — the bug is only in the generate path
On mvault v0.11.1 (loaf 2.0.0-alpha.6) I pre-staged a curated section before running loaf release --bump patch --yes:
## [Unreleased]
### Fixed
- ...curated prose...
The release converted it correctly: ## [0.11.1] - 2026-07-17 landed in the right place (directly under the header block, above ## [0.11.0]), and a fresh ## [Unreleased] / "No unreleased changes yet." stub was reinserted above it. No manual relocation needed.
Every earlier occurrence of the bottom-append (v0.11.0 and at least three before it) ran without a pre-existing [Unreleased] section, i.e. through the generate-from-commits fallback.
So: the [Unreleased]→version conversion inserts at the correct position; the generate-from-commits fallback appends at EOF. The fix is presumably to make the fallback insert at the same anchor the conversion path uses (after the intro/header block, before the first existing ## [ heading), and ideally to also insert the fresh [Unreleased] stub in that path.
Workaround
Pre-stage a curated ## [Unreleased] section before running loaf release — which is arguably the better authoring flow anyway, but the fallback shouldn't corrupt file order when it's used.
Summary
When
loaf releasehas to generate the new changelog section itself (no## [Unreleased]section present inCHANGELOG.md), the new## [X.Y.Z]section is appended at the bottom of the file instead of the newest-first position under the header. Onlevifig/mvaultthis was manually relocated after four separate releases.Narrowing evidence — the bug is only in the generate path
On mvault v0.11.1 (loaf 2.0.0-alpha.6) I pre-staged a curated section before running
loaf release --bump patch --yes:The release converted it correctly:
## [0.11.1] - 2026-07-17landed in the right place (directly under the header block, above## [0.11.0]), and a fresh## [Unreleased]/ "No unreleased changes yet." stub was reinserted above it. No manual relocation needed.Every earlier occurrence of the bottom-append (v0.11.0 and at least three before it) ran without a pre-existing
[Unreleased]section, i.e. through the generate-from-commits fallback.So: the
[Unreleased]→version conversion inserts at the correct position; the generate-from-commits fallback appends at EOF. The fix is presumably to make the fallback insert at the same anchor the conversion path uses (after the intro/header block, before the first existing## [heading), and ideally to also insert the fresh[Unreleased]stub in that path.Workaround
Pre-stage a curated
## [Unreleased]section before runningloaf release— which is arguably the better authoring flow anyway, but the fallback shouldn't corrupt file order when it's used.