Summary
When two commits of the same type (e.g. two fix commits) land in the same release, and the breaking one is not the most-recently-authored commit of that type, its BREAKING CHANGE: description is silently omitted from the changelog's ### Breaking Changes section, even though the release's version number is still correctly bumped to major.
This affects .components/changes.md.j2 (both the md and rst template sets), and the identical bug pattern is duplicated for the "Additional Release Information" / release-notices section.
Version
python-semantic-release==10.6.1
Minimal reproduction
mkdir psr-repro && cd psr-repro
git init -q
git config user.email "test@example.com"
git config user.name "test"
git remote add origin https://github.com/example/example.git
cat > pyproject.toml <<'EOF'
[project]
name = "psr-repro"
version = "0.0.0"
[tool.semantic_release]
version_variables = []
tag_format = "v{version}"
[tool.semantic_release.branches.main]
match = "main"
prerelease = false
[tool.semantic_release.commit_parser_options]
minor_tags = ["feat", "feature"]
patch_tags = ["fix", "perf"]
[tool.semantic_release.changelog]
mode = "update"
insertion_flag = "<!-- version list -->"
EOF
git add pyproject.toml
git commit -q -m "chore: init"
git tag v0.0.0
git branch -m main
# Breaking commit, committed FIRST (older)
git commit -q --allow-empty -m "fix!: breaking change committed first (older)
BREAKING CHANGE: this description should appear in the changelog"
# Non-breaking commit of the SAME type, committed SECOND (newer)
git commit -q --allow-empty -m "fix: unrelated fix committed second (newer)"
pip install python-semantic-release==10.6.1
echo "=== version (correct) ==="
semantic-release --noop version --print
echo "=== changelog (Breaking Changes section missing) ==="
semantic-release changelog
cat CHANGELOG.md
Expected behavior
The generated CHANGELOG.md should contain a ### Breaking Changes section with the description this description should appear in the changelog, since one of the two fix commits in this release has a BREAKING CHANGE: footer.
Actual behavior
version --print correctly outputs 1.0.0 (the version-bump calculation in semantic_release/version/algorithm.py's next_version() scans every parsed commit's .bump level independently and is unaffected).
CHANGELOG.md only contains a ### Bug Fixes section listing both commits. No ### Breaking Changes section is rendered at all, so the breaking-change description is silently lost:
## Unreleased
### Bug Fixes
- Breaking change committed first (older)
([`6a33870`](.../commit/6a33870...))
- Unrelated fix committed second (newer)
([`143d5d1`](.../commit/143d5d1...))
Root cause
In semantic_release/data/templates/conventional/md/.components/changes.md.j2 (and the rst equivalent):
{% set breaking_commits = commit_objects | map(attribute="1.0")
%}{% set breaking_commits = breaking_commits | rejectattr("error", "defined") | selectattr("breaking_descriptions.0") | list
commit_objects is a list of (type_name, commits) tuples, e.g. ("Bug Fixes", [c1, c2]), where each type's commits list is ordered newest commit first. map(attribute="1.0") resolves the dotted path tuple[1][0] - i.e. it extracts only the single newest commit within each type bucket, then checks that one commit for breaking_descriptions. Any breaking commit that isn't the most recently authored commit of its own type is never inspected, so its breaking-change prose can never appear.
The identical pattern (commit_objects | map(attribute="1.0")) is duplicated later in the same file for building notice_commits (release notices), so the same bug affects the "Additional Release Information" section too:
semantic_release/data/templates/conventional/md/.components/changes.md.j2:70 and :103
semantic_release/data/templates/conventional/rst/.components/changes.rst.j2:98 and :132
Suggested fix
The breaking/notice filtering should scan all commits across all type buckets (flatten commit_objects to every individual commit first), not just index 0 of each bucket. This also matches the surrounding comment's stated intent ("2. Peel off the outer list to get a list of ParsedCommit objects") - the current .0 grabs one element instead of flattening the whole list:
{% set breaking_commits = commit_objects | map(attribute="1") | sum(start=[])
%}{% set breaking_commits = breaking_commits | rejectattr("error", "defined") | selectattr("breaking_descriptions.0") | list
I patched a local copy of changes.md.j2 with this exact change (both the breaking_commits and notice_commits assignments) and re-ran the reproduction above plus a few regression cases against it:
- The original 2-commit repro above -> now correctly renders the
### Breaking Changes section.
- A single breaking commit alone in a release -> still renders correctly (no regression).
- 3 commits (2 normal + 1 breaking, breaking committed last/newest - the case that happened to work by accident before the fix) -> still renders correctly.
- A release with no breaking commits at all -> still correctly omits the
### Breaking Changes section entirely (no false positives).
Happy to open a PR with this change (both md and rst templates) if that's useful, rather than just reporting it.
Impact note
To be clear about severity: this only affects changelog/release-notes prose completeness. The version-bump number itself is computed correctly and is unaffected, since that calculation scans every parsed commit independently rather than grouping by type. Still, silently dropping a breaking-change description from the changelog is a real correctness issue for anyone relying on the generated changelog/release notes to communicate breaking changes to consumers.
Summary
When two commits of the same type (e.g. two
fixcommits) land in the same release, and the breaking one is not the most-recently-authored commit of that type, itsBREAKING CHANGE:description is silently omitted from the changelog's### Breaking Changessection, even though the release's version number is still correctly bumped to major.This affects
.components/changes.md.j2(both themdandrsttemplate sets), and the identical bug pattern is duplicated for the "Additional Release Information" / release-notices section.Version
python-semantic-release==10.6.1Minimal reproduction
Expected behavior
The generated
CHANGELOG.mdshould contain a### Breaking Changessection with the descriptionthis description should appear in the changelog, since one of the twofixcommits in this release has aBREAKING CHANGE:footer.Actual behavior
version --printcorrectly outputs1.0.0(the version-bump calculation insemantic_release/version/algorithm.py'snext_version()scans every parsed commit's.bumplevel independently and is unaffected).CHANGELOG.mdonly contains a### Bug Fixessection listing both commits. No### Breaking Changessection is rendered at all, so the breaking-change description is silently lost:Root cause
In
semantic_release/data/templates/conventional/md/.components/changes.md.j2(and therstequivalent):commit_objectsis a list of(type_name, commits)tuples, e.g.("Bug Fixes", [c1, c2]), where each type'scommitslist is ordered newest commit first.map(attribute="1.0")resolves the dotted pathtuple[1][0]- i.e. it extracts only the single newest commit within each type bucket, then checks that one commit forbreaking_descriptions. Any breaking commit that isn't the most recently authored commit of its own type is never inspected, so its breaking-change prose can never appear.The identical pattern (
commit_objects | map(attribute="1.0")) is duplicated later in the same file for buildingnotice_commits(release notices), so the same bug affects the "Additional Release Information" section too:semantic_release/data/templates/conventional/md/.components/changes.md.j2:70and:103semantic_release/data/templates/conventional/rst/.components/changes.rst.j2:98and:132Suggested fix
The breaking/notice filtering should scan all commits across all type buckets (flatten
commit_objectsto every individual commit first), not just index0of each bucket. This also matches the surrounding comment's stated intent ("2. Peel off the outer list to get a list of ParsedCommit objects") - the current.0grabs one element instead of flattening the whole list:I patched a local copy of
changes.md.j2with this exact change (both thebreaking_commitsandnotice_commitsassignments) and re-ran the reproduction above plus a few regression cases against it:### Breaking Changessection.### Breaking Changessection entirely (no false positives).Happy to open a PR with this change (both
mdandrsttemplates) if that's useful, rather than just reporting it.Impact note
To be clear about severity: this only affects changelog/release-notes prose completeness. The version-bump number itself is computed correctly and is unaffected, since that calculation scans every parsed commit independently rather than grouping by type. Still, silently dropping a breaking-change description from the changelog is a real correctness issue for anyone relying on the generated changelog/release notes to communicate breaking changes to consumers.