Moving or deleting a published tag Jump to heading

A release tag is a promise: v2.4.0 means this exact code, for everyone, forever. Package managers cache it, deploy systems pin it, security scanners record it, and users’ clones keep whatever value they first fetched. Git does not propagate tag changes the way it propagates branch updates β€” a clone that already has v2.4.0 will not update it on a normal fetch. So moving a published tag does not fix the release everywhere; it splits it, with different people holding different commits under the same name. Sometimes a tag is wrong and must change β€” it was pushed to the wrong commit minutes ago, or it points at a commit containing a leaked secret. This page covers the narrow cases where moving is justified, how to do it with the least damage, and the usually better alternative, within release tagging and versioning.

When to use this approach Jump to heading

  • A release tag was pushed to the wrong commit.
  • A tagged release contains something that must not be distributed β€” a secret, licensed material.
  • Someone proposes β€œjust moving the tag” to include a last-minute fix.
  • You need to remove a tag created by mistake, such as a typo in the version.

Step 1 β€” Understand why tags do not propagate Jump to heading

Branches are expected to move, and fetch updates them. Tags are expected not to, so git fetch does not overwrite an existing local tag with a different value unless forced. The result after moving a tag on the server is divergence.

# On a clone that fetched v2.4.0 before it was moved
git fetch --tags
# ! [rejected]        v2.4.0     -> v2.4.0  (would clobber existing tag)
git rev-parse v2.4.0      # still the old commit
What happens when a published tag movesA maintainer moves v2.4.0 on the server to a new commit. A clone that fetched earlier keeps the old value because fetch refuses to clobber existing tags. A new clone gets the new value. A package cache that recorded the old commit keeps serving it. Three consumers now disagree about what v2.4.0 is.maintainerserverold clonenew cloneforce-push v2.4.0 β†’ newfetch --tagsrejected: would clobberclonev2.4.0 = new committhe tag name now means two different things, depending on when you fetched

Step 2 β€” Prefer a new version Jump to heading

Almost always, the right fix for a wrong or incomplete release is a new version. Release v2.4.1 with the fix, mark v2.4.0 as superseded in release notes, and leave its tag alone. Nobody’s cache, pin or record becomes wrong.

git tag -s v2.4.1 -m "Release 2.4.1 β€” supersedes 2.4.0 (missing migration)" "$fixed_commit"
git push origin v2.4.1
gh release edit v2.4.0 --notes "⚠️ Superseded by v2.4.1: 2.4.0 is missing a database migration. Do not deploy."
A release tag is wrong β€” what now?If the tag has only just been pushed and nothing has consumed it, moving it with notice is acceptable. If it has been consumed by users, packages or deploys, release a new version and mark the old one superseded. If it contains something that must not be distributed, delete or move it, rotate any secret, and accept the divergence as the lesser harm.Has anything consumed the tag yet?no β€” pushed minutes agoMove with noticeforce-push + announceyes β€” fetched, packagedNew versionmark old supersededcontains a secretDelete / moverotate, accept divergencethe middle branch is the answer far more often than people expect

Step 3 β€” If you must move it, do it immediately and loudly Jump to heading

When a tag was pushed to the wrong commit seconds or minutes ago, before any release job or user consumed it, moving it is reasonable. Do it at once, and tell everyone who might have fetched.

git tag -d v2.4.0
git tag -s v2.4.0 "$correct_commit" -m "Release 2.4.0"
git push --force origin refs/tags/v2.4.0
# Tell anyone who fetched in the meantime to refresh their local tag:
#   git fetch --force origin 'refs/tags/v2.4.0:refs/tags/v2.4.0'

Check whether a release workflow already ran on the old value; if it published artefacts, the tag has been consumed and step 2 applies instead.

⚠️ SAFETY WARNING: Force-pushing a tag that package registries, deploy systems or users have already consumed creates two versions of the same release. Registries usually refuse to republish a version, so the artefacts stay built from the old commit while the tag points at the new one. If that has already happened, release a new version rather than compounding the mismatch.

Step 4 β€” Delete a tag created by mistake Jump to heading

A tag with a typo β€” v.2.4.0, v2.40 β€” can be deleted if nothing depends on it. Deleting on the server does not delete it from clones that fetched it; they must prune explicitly.

git push origin --delete v2.40
git tag -d v2.40
# Others:
git fetch --prune --prune-tags origin

Step 5 β€” Handle a tag that contains a secret Jump to heading

If a tagged commit contains a leaked credential, the tag keeps it reachable even after branches are rewritten. Revoke the credential first β€” that is the actual fix β€” then decide whether to rewrite history and move or delete the tag, as described in removing a leaked secret from Git history. Here, divergence is the lesser harm.

Step 6 β€” Prevent accidental moves Jump to heading

Protect release tags on the server so they cannot be updated or deleted without a deliberate override, and create them only from a release workflow.

# Ruleset on refs/tags/v*: block updates and deletions; restrict creation to the release app
gh api "repos/$OWNER/$REPO/rulesets" --jq '.[] | select(.target=="tag") | {name, enforcement}'
Moving a tag versus releasing a new versionMoving a consumed tag leaves old clones, package caches and deploy records disagreeing about what the version means, and registries usually refuse the re-publish. Releasing a new patch version keeps every existing record correct and makes the fix visible in the version number itself.Move v2.4.0Release v2.4.1old cloneskeep old commitunaffectedpackage registryrefuses re-publishnew versiondeploy recordsnow ambiguousstill accurateusers see the fixonly if they re-fetchin the versiona new version costs one number; a moved tag costs everyone's trust in the old one

Pipeline-created tags with guards are covered in signing release tags from a pipeline.

Validation checklist Jump to heading

Frequently Asked Questions Jump to heading

Why does git pull not update a moved tag? Jump to heading

Fetch refuses to overwrite an existing tag with a different value, to protect against silent changes. Updating requires git fetch --force with an explicit refspec, which most people never run.

Can we move a tag if we also re-publish the package? Jump to heading

Most registries forbid republishing the same version, precisely to keep versions immutable. That alone makes moving consumed tags a dead end.

Is deleting a tag ever better than moving it? Jump to heading

When the version should never have existed β€” a typo or an accidental release β€” deleting and releasing the next version avoids confusion. Deleting a real release breaks everyone who depended on it.