Skip to content

Breaking Changes / release-notices section only inspects the newest commit per type bucket, silently dropping other breaking commits #1471

Description

@DillonSadofsky

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions