Skip to content

semantic-release: bang-style breaking commits (feat!:) produce no release at all #653

Description

@devantler

🤖 Generated by the Daily AI Engineer

Evidence

While wiring create-release.yaml into devantler-tech/world-at-ruin, I measured what the
portfolio's standard .releaserc actually does with a bang-style breaking commit. Tested with
semantic-release@25.0.3 (the exact version create-release.yaml pins) against a clean baseline —
a local tag at HEAD, so only the test commit is analysed:

commit bare .releaserc expected
feat!: … no release at all major
fix!: … no release at all major
feat(scope)!: … no release at all major
feat: … minor minor
fix: … patch patch
docs: / chore: / ci: no release no release
feat: + BREAKING CHANGE: footer major major

The failure mode is not a wrong bump — a bang-style breaking change produces no release
whatsoever
, so it ships silently unversioned and consumers never learn the API broke.

Cause: @semantic-release/commit-analyzer defaults to the Angular preset, which recognises
only the BREAKING CHANGE: footer, not the Conventional Commits ! shorthand.

Why this is portfolio-wide, not a world-at-ruin quirk

Every .releaserc in the org is the bare form (ksail, go-template, dotnet-template,
platform, platform-template, gitops-tenant-template, aws, .github, wedding-app,
ascoachingogvaner, and actions' own test fixture) — some with extra releaseRules, none
configuring the bang.

Meanwhile the org commit-message ruleset explicitly permits it:

^(build|chore|ci|docs|feat|fix|perf|refactor|revert|style|test){1}(\([\w\-\.]+\))?(!)?:
                                                                                ^^^^^

So the org's own rules invite feat!:, and the release pipeline then silently drops it. Any repo
here is one bang-commit away from an unreleased breaking change.

Hypothesis

Fixing this centrally in create-release.yaml removes the trap for every consumer at once, rather
than requiring each repo to discover it independently.

Proposed direction

Two options, and I'd like a steer:

  1. Install the preset in the shared workflow — add
    conventional-changelog-conventionalcommits to the npx invocation so consumers can set
    "preset": "conventionalcommits", which handles ! natively. Verified it is not currently
    available: with the bare npx semantic-release@25.0.3, that preset fails with
    Cannot find module 'conventional-changelog-conventionalcommits'. Cleanest semantically, but
    changes the shared workflow's dependency surface for everyone.
  2. Document the per-repo .releaserc fixparserOpts.breakingHeaderPattern plus a
    breaking: true release rule, which works with only bundled dependencies. This is what
    world-at-ruin now ships (ci: split CI from CD and adopt semantic-release via .releaserc world-at-ruin#108); verified across all eight commit
    forms above, with the BREAKING CHANGE: footer path unaffected.

Option 1 is the better fix if the added dependency is acceptable; option 2 is already proven and
needs no change here.

Acceptance criteria

  • feat!: / fix!: / feat(scope)!: produce a major release on a repo using the shared
    workflow.
  • feat:, fix:, docs:, chore:, ci: and the BREAKING CHANGE: footer path are
    unchanged (verified, not assumed — this is the regression risk).
  • Existing consumers are migrated or explicitly confirmed unaffected.

Size

Small-to-medium — the change is tiny; the care is in not regressing bump behaviour across ~10 repos.

Metadata

Metadata

Assignees

No one assigned

    Type

    Fields

    No fields configured for Bug.

    Projects

    Status
    ✅ Done

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions