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 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." 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}' 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.
Related Jump to heading
- Release Tagging & Versioning β the parent topic.
- Annotated vs Lightweight Tags β replacing lightweight tags, a common reason to move one.
- Responding to a Leaked Credential β the secret case in full.
- Triggering Workflows on Tags and Releases β release jobs that a moved tag re-triggers.