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:
-
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.
-
Push changes and tags runs:
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
- Create
release/0.18.0 from main.
- 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.
- Open a PR from
release/0.18.0 to main and "Squash and merge" it.
- 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
- 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.
- GitHub Release target drift — the published Release points at a commit not on
main.
- 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)
- 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.
- 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.
- 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
Summary
The
actions/release/githubcomposite action creates and pushes the git tag from whatever branch it runson. When a consuming repo runs the action on a short-lived
release/<version>branch and then squash-mergesthat 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, abroken 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(refgithub-release/v1), fileactions/release/github/action.yml.Root cause
Two steps in the composite action, in order:
Generate changelog and bump version with commitizen runs:
cz bumpcreates a commit (bump: version X → Y) and a git tag on the currentHEAD. When the actionruns on a
release/<version>branch, that commit/tag are created on the release branch.Push changes and tags runs:
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 taggedbump:commit is never an ancestor ofmain. The tag is left dangling.Reproduction
release/0.18.0frommain.github-releaseworkflow on that branch (this action). It bumps the version, writes the changelog,creates tag
0.18.0on the branchbump:commit, and pushes commit + tag.release/0.18.0tomainand "Squash and merge" it.mainnow has a singlerelease: 0.18.0 (#NN)commit; tag0.18.0points at the discarded branchcommit and is not reachable from
main.(
Rebase and mergeorphans it the same way, because it rewrites commit SHAs. Only a true merge commit orfast-forward preserves the tagged commit.)
Evidence (from a consumer repo: serapeum-org/cleopatra)
Every release from
0.8.0through0.17.0in that repo is orphaned this way. (Several versions ended up with noreachable tag at all.)
Impact
main, sogit describe, "compare" links, and anything thatchecks out a release tag get a commit that isn't on the mainline.
main.changelog_incremental/
update_changelog_on_bump) computes "commits since the last version tag". Because the previous release's tag isorphaned (not in
main's history), the nextcz bumpcannot resolve the range and lumps everything into asingle
Unreleasedblock instead of producing the new version section. In cleopatra this silently froze thein-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
mainwithout post-processing. The action should either not tag the branch, or document/guardthe constraint.
Proposed fixes (any one resolves it)
create-tag: false(ortag-on: branch|none). When tagging is disabled, runcz bump --files-only(updates version + changelog, nocommit, no tag) and let the consumer commit/merge normally, then create the tag on the post-merge
maincommit ina separate step/workflow. This makes the action compatible with squash-merge release flows out of the box.
main(socz bump's tag lands on a commit that is onmain), with the bump commit pushed tomain.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
release/<version>branch, squash-merge tomain, and end up with theversion tag reachable from
main(e.g. via acreate-tag: false+ post-merge tag step, or documented guidance).cz bump/cz changelogruns resolve the previous version correctly (no spuriousUnreleasedlumping).