Skip to content

release/github: tag is orphaned when the release branch is squash-merged (breaks reachable tags + incremental changelog) #9

Description

@MAfarrag

Summary

The actions/release/github composite action creates and pushes the git tag from whatever branch it runs
on
. When a consuming repo runs the action on a short-lived release/<version> branch and then squash-merges
that branch into main (a very common release workflow), the squash discards the exact commit the tag points to.
The result is an orphaned tag — a version tag that is not reachable from main — and, as a knock-on effect, a
broken incremental changelog on subsequent releases.

This is a design mismatch between the action (which assumes the branch it tags becomes permanent history) and the
"release branch + squash-merge" workflow.

Affected action

actions/release/github (ref github-release/v1), file actions/release/github/action.yml.

Root cause

Two steps in the composite action, in order:

  1. Generate changelog and bump version with commitizen runs:

    $CZ_CMD bump --yes --increment ${{ inputs.increment }}

    cz bump creates a commit (bump: version X → Y) and a git tag on the current HEAD. When the action
    runs on a release/<version> branch, that commit/tag are created on the release branch.

  2. Push changes and tags runs:

    git push
    git push --tags

    This pushes the branch commit and the tag pointing at it.

The action therefore hard-couples "bump the version files + changelog" with "create the git tag", and emits the
tag on the branch commit. It implicitly assumes that branch commit survives into the default branch unchanged
(direct push, fast-forward, or a merge commit).

It does not survive a squash-merge: squashing replaces all the branch commits with a single brand-new commit
on main, so the tagged bump: commit is never an ancestor of main. The tag is left dangling.

Reproduction

  1. Create release/0.18.0 from main.
  2. Run the github-release workflow on that branch (this action). It bumps the version, writes the changelog,
    creates tag 0.18.0 on the branch bump: commit, and pushes commit + tag.
  3. Open a PR from release/0.18.0 to main and "Squash and merge" it.
  4. Observe: main now has a single release: 0.18.0 (#NN) commit; tag 0.18.0 points at the discarded branch
    commit and is not reachable from main.

(Rebase and merge orphans it the same way, because it rewrites commit SHAs. Only a true merge commit or
fast-forward preserves the tagged commit.)

Evidence (from a consumer repo: serapeum-org/cleopatra)

# Tagged commit for 0.17.0 is the branch bump commit:
$ git log -1 --format='%h %s' 0.17.0
efde8ac bump: version 0.16.0 → 0.17.0

# It is NOT reachable from main:
$ git merge-base --is-ancestor efde8ac main && echo reachable || echo ORPHANED
ORPHANED

# main instead has a squashed release commit (single parent = squash, not a merge):
$ git rev-list --parents -n1 <main 0.17.0 commit>   ->  1 parent
   25eb474 release: 0.17.0 (#167)

Every release from 0.8.0 through 0.17.0 in that repo is orphaned this way. (Several versions ended up with no
reachable tag at all
.)

Impact

  1. Orphaned tags — version tags don't point to main, so git describe, "compare" links, and anything that
    checks out a release tag get a commit that isn't on the mainline.
  2. GitHub Release target drift — the published Release points at a commit not on main.
  3. Broken incremental changelog (the costly one) — commitizen's incremental changelog (changelog_incremental
    / update_changelog_on_bump) computes "commits since the last version tag". Because the previous release's tag is
    orphaned (not in main's history), the next cz bump cannot resolve the range and lumps everything into a
    single Unreleased block
    instead of producing the new version section. In cleopatra this silently froze the
    in-repo changelog for nine releases (0.8.0–0.17.0) until it was discovered and backfilled by hand.

Why this belongs in the action

The action is the component that decides where and when the tag is created (during the bump, on the branch) and
that it is pushed from the branch. A consumer using a standard "release branch + squash-merge" flow has no way to
make the tag land on main without post-processing. The action should either not tag the branch, or document/guard
the constraint.

Proposed fixes (any one resolves it)

  1. Decouple bump-files from tagging (recommended). Add an input such as create-tag: false (or
    tag-on: branch|none). When tagging is disabled, run cz bump --files-only (updates version + changelog, no
    commit, no tag) and let the consumer commit/merge normally, then create the tag on the post-merge main commit in
    a separate step/workflow. This makes the action compatible with squash-merge release flows out of the box.
  2. Support a "bump on default branch" mode. Document/encourage running the action directly on main (so
    cz bump's tag lands on a commit that is on main), with the bump commit pushed to main.
  3. Document the hard constraint. At minimum, state in the action README that the branch it runs on must reach the
    default branch via a merge commit or fast-forward, and that squash/rebase merges will orphan the tag and
    break incremental changelogs
    . Optionally fail fast if the tagged commit is detected to be non-reachable after
    merge.

A combination of (1) for flexibility and (3) for safety would be ideal.

Acceptance criteria

  • A consumer can run the action on a release/<version> branch, squash-merge to main, and end up with the
    version tag reachable from main (e.g. via a create-tag: false + post-merge tag step, or documented guidance).
  • Subsequent incremental cz bump / cz changelog runs resolve the previous version correctly (no spurious
    Unreleased lumping).
  • The README documents the supported merge strategies and the tagging behavior.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions