You tagged a commit, pushed the tag, and then realized the tag belongs on a newer commit instead. Maybe a fix landed right after you cut the release, or you tagged one commit too early. Moving the tag locally is the easy part. The push is what trips people up: a normal git push gets rejected because the tag already exists on the remote, and Git will not quietly overwrite it for you.
A tag is just a named pointer at a commit. Git treats tags as fixed once they are published, which is usually what you want, so it refuses to update a remote tag on an ordinary push. Moving one is a deliberate two-step operation: repoint the tag locally, then force that change to the remote.
Moving the tag locally
If you are already sitting on the commit you want, git tag -f v1.2.0 re-creates the tag right there. If the target is a different commit, name it explicitly with its SHA:
git tag -f v1.2.0 <commit-sha>
The -f (short for --force) is the part that lets an existing tag be replaced. Without it, Git tells you the tag already exists and stops. If your tag is annotated rather than lightweight, keep it annotated when you move it so you do not drop the message and tagger details:
git tag -f -a v1.2.0 <commit-sha> -m "Release v1.2.0"
Every flag here is described in the git tag documentation, and if you are unsure which kind of tag you are holding, the Pro Git chapter on tagging explains the lightweight versus annotated split.
Force-pushing the tag to the remote
Now push it, and watch the plain command fail. If you run git push origin v1.2.0 after moving the tag, you get:
! [rejected] v1.2.0 -> v1.2.0 (already exists)
error: failed to push some refs to 'github.com:you/repo.git'
hint: Updates were rejected because the tag already exists in the remote.
The remote tag still points at the old commit, and Git will not move it on its own. Force the update:
git push origin v1.2.0 --force
And, done! The remote tag now points at the new commit.
Why a plain git fetch keeps the old tag
Here is the part that trips people up. Moving a tag on the remote does not update anyone else’s copy. A plain git fetch will not overwrite a local tag that already exists, so a teammate who fetched the old tag stays pinned to the old commit and might build from it without realizing anything changed. They have to force it on their end:
git fetch --tags --force
It is the same reason your push needed --force: Git guards existing tags by default on both sides. The matching push and fetch behavior lives in the git push documentation. So when you move a published tag, give a quick heads-up to anyone working off it, especially if that tag drives a release or a deploy.
Redoing the GitHub release
If a GitHub release is attached to the tag, the release follows the tag once you move it, so editing the existing release and saving is usually enough. You can also delete the release and recreate it against the moved tag. One current caveat: if the repository has immutable releases turned on, a published release only lets you edit its title and notes afterward, so the cleaner route there is to start from a draft and publish once the assets are attached. GitHub’s guide to managing releases walks through both the web UI and the gh CLI.
Git treats tags as immutable on purpose, so moving one is always delete-and-replace with a force on the way out, and a force on the way in for everyone else. If the tag is already public and people depend on it, cutting a fresh version is usually safer than moving the old one. Save the move for when the tag genuinely has to keep the same name.