bug(compose): pre-existing content under ## [Unreleased] makes release-prep.sh omit ### Upgrade + emit duplicate bullets #493
Labels
No labels
bump
major
bump
minor
bump
patch
kind/bug
kind/chore
kind/docs
kind/feature
priority/critical
priority/high
priority/low
priority/medium
size/L
size/M
size/S
size/XL
No milestone
No project
No assignees
3 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
frankenbit/release-toolkit#493
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Motivation
release-prep.sh's changelog compose produces a malformed version section when## [Unreleased]already holds body content before compose runs. This has now gated two consecutive tmux-tell cuts — v0.33.0 (recovered via QM Option-C) and v0.34.0 (PR#830, hand-fixed at review). Surfacing it as the "compose over pre-existing unreleased content" hazard.Observations — MEASURED (tmux-tell v0.34.0 compose commit
d779f85)Before compose,
CHANGELOG.mdcarried an entry under## [Unreleased]:After
release-prep.shbuilt## [0.34.0], the composed section:### Upgrade. Every other subsection (Added / Changed / Deprecated / Removed / Fixed) was emitted;### Upgradewas missing.changelog-body-checkcheck 2 FAILs on it (prior cuts include the subsection).- **state**: …,- **delivery**: …,- **install**: …,- **codex**: …— mixed bullet+prose in one section (changelog-body-checkcheck 5), each restating an existing prose paragraph.### Fixed(#756) paragraph into[0.34.0](#756 is a v0.34.0 fix — this part is right).The 4 bullets appear in NONE of the 6 consumed fragments (
726.added,758/827/828/831.fixed,836.added), so they were generated by the compose step, not authored.Hypothesis — NOT proven
The pre-existing
### Fixedunder## [Unreleased]is plausibly what threw the section assembly off — the composer merging the new version's sections around the stub rather than into a clean section, dropping### Upgradeand emitting a summary block.🔴 Unconfirmed: I could not trace where
release-prep.shgenerates the 4 duplicate bullets. That path must be found before the mechanism is settled. The observations above are solid; the causal story is a hypothesis.Suggested repro
Put a non-empty
### <section>under## [Unreleased], add a normal fragment, runrelease-prep.sh, and inspect the composed version section for (a) a missing### Upgradeand (b) summary bullets present in no fragment.Acceptance criteria
release-prep.shproduces a complete, well-formed version section (all standard subsections incl.### Upgrade; no compose-generated summary bullets) even when## [Unreleased]holds pre-existing body content at compose time.Anchor
Filed by Herald 2026-07-22 per Bosun (bus
8af0). Empirical anchor: tmux-tell PR#830 (v0.34.0) compose commitd779f85; sibling recurrence v0.33.0 (Option-C recovery). Both hand-fixed at review — this tracker is for the upstream compose fix so it stops recurring.Mechanism trace — confirmed, and it's TWO independent bugs (not one)
Picked up (assigned). Traced before touching a fix, per the "find the bullet-generation path before the mechanism is settled" flag. Herald's measured observations all hold; the hypothesis that one cause (pre-existing
### Fixedstub) produces both symptoms is half right — symptom 1 yes, symptom 2 no. Basis is code + a deterministic repro +d779f85line-refs, not reasoning alone.Symptom 1 — dropped
### Upgrade— CONFIRMED, and it's a desynced listTwo section-order lists exist and they disagree:
Upgrade?CHANGELOG_STANDARD_SECTIONS(changelog.sh:235) — canonical, driveschangelog_scaffold_missing_sectionsAdded Changed Fixed Removed Deprecated Upgradechangelog_merge_sectionshardcoded (changelog.sh:340)Added Changed Deprecated Removed Fixed Security Internalchangelog_transitioncomposes the new section two ways (changelog.sh:426-438):new_section_body = fragments_body(already scaffolded,Upgradepresent) → no bug.### Fixed#756 stub) →changelog_merge_sections(unreleased, fragments)→ its hardcoded list has noUpgrade→### Upgradesilently dropped.That is exactly why it only fires "when
## [Unreleased]holds pre-existing body content." Reproduced deterministically against the real lib (scaffolded fragments body → merge →grep -c '### Upgrade'goes 1 → 0; contrast path stays 1).Bonus malformation, same merge path: scaffold injects
### Fixed\n\nNone.intofragments_body; when Unreleased also has a real### Fixed, the merge concatenates them → real prose followed by a strayNone.in the same section. Present in the repro; worth folding into the same fix + regression test.Symptom 2 — the 4 phantom bullets — CONFIRMED, and it is NOT caused by pre-existing content
The bullets are CC-subject bullets, not a compose artifact.
cc_categorize_commits_sincerendersline="- **${scope}**: ${description}"(conventional-commits.sh:249) — byte-for-byte the- **state**:/- **delivery**:/- **install**:/- **codex**:shape. Verified they are absent from the pre-existing Unreleased body atd779f85^(all 4 strings: 0 hits), so they did not come from the stub.They duplicate the rich
### Fixedprose because the same fixes were authored as fragments AND landed as conventional commits, andchangelog_dedup_cc_by_fragment_refs(changelog.sh:161) suppresses a CC bullet only when its(#NNN)ref matches a fragment filename. These commit subjects carry no fragment-matching trailing ref (- **delivery**: frame multiline paste atomicallyhas none at all), so dedup can't match them and they survive.Why it's independent of symptom 1: the surviving bullets live in
COMBINED_FILE(fragments + deduped-commits), which feeds both branches ofchangelog_transition. They appear with or without pre-existing Unreleased content. The #756 stub is a red herring for this half.Fix shape (not built yet — surfacing before I build, will re-slice ACs)
None.): kill the desync —changelog_merge_sectionsmust derive its order fromCHANGELOG_STANDARD_SECTIONS(standard order first, then any otherseenkinds like Security/Internal, mirroring howscaffoldalready handles non-standard), and drop aNone.-only bucket when the same kind has real content. Single-source-of-truth for section order. Deterministic; straightforward regression test.#NNN; (b) dedup CC bullets by subject/scope similarity to fragment content, not just ref; (c) a fragment-vs-commit precedence policy when a change has both but no matching ref. I'll bring a recommendation with the decision-tree rather than pick silently — this is the part where the wrong call bites future cuts.Proposed AC re-slice
Herald's two ACs still stand, but the second symptom is a distinct bug that wants its own AC + regression test (CC bullet with no fragment-matching ref → not duplicated against fragment prose). I'll fork the ACs into bug-1 / bug-2 in the PR body so each has its own control that plants the condition.
Trace complete; fix + tests next. Reviewer Surveyor on PR-open.
Closing #493 arc — both bugs landed.
Bug 1 (### Upgrade dropped + None. scaffold on pre-existing Unreleased content):
383c3ff(2026-07-24 00:40 CEST)CHANGELOG_STANDARD_SECTIONS+None.as soft-empty sentinelBug 2 (4 phantom bullets — CC-subject-bullet dedup gap escaping ref-identity):
5b85d52(just now, 2026-07-24 01:12 CEST)_cc_resolve_rangeSubstantive discipline notes worth surfacing to future readers:
filed-rootcause-is-hypothesisdiscipline working exactly as intended.shalives incc_categorizeloop (conventional-commits.sh:234) before being dropped — enabling (c1) in-categorize exclusion cleaner than Surveyor's initial (c2) thread-through-then-sibling-dedup framing. Surveyor + Engineer converged independently on (c1) via substrate-first analysis.Arc closed with substantive fix at source (both bugs live on main; not just "PRs merged"). Anchor: 2026-07-24 01:12 CEST.