You ran git tag v2.4.0 on the wrong commit — maybe the version bump commit was not yet in your working branch, or you tagged before the CI hotfix landed. Now git push --tags has already published v2.4.0 to GitHub, and your package registry (npm, PyPI, RubyGems) either picked up the wrong code or refuses to re-publish because the tag already exists. Moving a tag that has already been pushed is a destructive operation that requires coordination: any clone that fetched the old tag will not automatically see the moved one. This guide explains how to move the tag, notify consumers, and prevent accidental mistagging.
Common causes
Ordered by hit rate, highest first.
1. Tagged before the final version-bump commit landed
The release process commits a package.json version bump, but someone ran git tag v2.4.0 one commit too early — before the bump commit was merged or cherry-picked.
How to spot it: git show v2.4.0 --stat — if the package.json change is not in the tagged commit’s diff, the tag is one commit early.
2. Tagged on a branch instead of the merge commit
Developer tagged on release/2.4.0 branch tip, not on the merge commit into main. Consumers who clone main see a different commit than the one the tag points to.
How to spot it: git log --oneline --decorate main | head -5 — if v2.4.0 appears in the middle of the log rather than at the merge commit, the tag is on a branch-only commit.
3. Annotated tag created with wrong -m message pointing to wrong object
git tag -a v2.4.0 -m "release" <wrong-sha> — the SHA was miscopied from a Slack message. The tag object itself has a creation timestamp, tagger name, and an object pointer that is wrong.
How to spot it: git cat-file -p v2.4.0 for an annotated tag shows the object field — compare it with the intended commit SHA.
4. CI pipeline tagged the commit before the tests passed
The tagging step in CI runs in parallel with tests. Tests pass after tagging, but if a test had failed, the tag would point to broken code. A deployment from that tag would ship bad code.
How to spot it: Check the CI run timeline — the “create tag” job ran before the “run tests” job completed.
5. Tag was pushed from a stale local clone
Developer’s local main was two commits behind the remote. They ran git tag v2.4.0 && git push --tags locally, tagging their out-of-date local HEAD.
How to spot it: git fetch --all then git log --oneline v2.4.0..origin/main — if this shows commits, the tag predates some remote commits that should have been included.
Shortest path to fix
Step 1: Confirm the correct target commit
git log --oneline main | head -10
Identify the SHA of the commit you intended to tag — call it CORRECT_SHA.
Step 2: Delete the tag locally
git tag -d v2.4.0
Step 3: Re-create the tag at the correct commit
# Lightweight tag
git tag v2.4.0 CORRECT_SHA
# Annotated tag (preferred for releases)
git tag -a v2.4.0 CORRECT_SHA -m "Release v2.4.0: add OAuth2 support and fix session expiry"
Step 4: Force-push the corrected tag to the remote
git push origin :refs/tags/v2.4.0 # delete remote tag
git push origin refs/tags/v2.4.0 # push corrected tag
Or in a single command:
git push origin --force --tags
Step 5: Notify teammates to update their local tags
Anyone who already fetched the old tag must run:
git fetch --tags --force
# or specifically:
git fetch origin refs/tags/v2.4.0:refs/tags/v2.4.0 --force
Step 6: Invalidate package registry caches
For npm packages:
# You cannot re-publish the same version to npm once published.
# Publish as v2.4.1 with the corrected code and deprecate v2.4.0:
npm deprecate package@2.4.0 "Incorrect code — use v2.4.1"
npm publish # after bumping version to 2.4.1
For GitHub Releases: delete the release, recreate it pointing at the correct tag.
Prevention
- Automate tagging in CI rather than running it locally — the CI pipeline knows the exact commit SHA after all jobs pass.
- Use a tagging script that verifies the target commit is the HEAD of
mainand all required CI checks are green before creating the tag. - Require annotated tags for releases:
git config --global tag.sort version:refnameand usegit tag -a -s v2.4.0with a GPG signature to create a tamper-evident record. - Add a pre-push hook that prevents pushing tags that point to commits not in
main’s history. - For packages published to registries, always verify the published artifact’s contents immediately after publication with
npm packor equivalent. - Use semantic-release or Release Please to automate version bumping, tagging, and changelog generation — remove the human error entirely from the tag creation step.
- Maintain a release checklist that includes “verify
git show v2.4.0 --statshows the version bump commit” as the first item.
FAQ
Q: I deleted and re-pushed the tag, but GitHub Releases still shows the old tag. Do I need to recreate the release? A: Yes. A GitHub Release is linked to a specific tag object. Delete the release (not just the tag) and recreate it, selecting the corrected tag.
Q: Can I move a tag without force-pushing? A: No. Tags are immutable references by convention. Moving one requires deleting the remote tag and pushing a new one with the same name, which requires a force-equivalent operation.
Q: We use annotated tags signed with GPG. Do I need to re-sign after moving the tag?
A: Yes. The tag object includes the SHA it points to, so re-signing is required. Use git tag -a -s v2.4.0 CORRECT_SHA.
Q: The wrong tag was already used by a downstream system to build a Docker image. What now? A: The Docker image is already built from the wrong commit. Rebuild the image from the corrected tag and re-deploy. Depending on your image retention policy, also retag or delete the incorrectly-built image in your registry.