This error occurs when you try to push a tag that already exists on the remote repository. Git rejects the push to prevent accidentally overwriting an existing tag. Fix by either deleting the remote tag first, force pushing, or using a different tag name.
When you push tags to a remote Git repository, Git checks whether those tags already exist on the remote. Unlike branches, which are designed to move forward with new commits, tags are meant to be immutable markers pointing to specific commits in your project's history. If a tag with the same name already exists on the remote and points to a different commit than your local tag, Git rejects the push with the message "! [rejected] <tag> -> <tag> (already exists)". This is a safety mechanism to prevent you from accidentally overwriting a tag that others may be relying on, since changing what a tag points to could cause confusion or break downstream processes. This error commonly occurs in several scenarios: - **You recreated a tag locally** after deleting it, but the remote still has the old version - **Someone else already pushed a tag** with the same name - **CI/CD pipelines** created the tag before your local push - **You're using a version number** that was already used in a previous release - **Automatic "Push all tags" settings** in GUI clients like SourceTree are trying to push old local tags
First, understand what your local tag points to versus what exists on the remote:
# Show what your local tag points to
git show-ref --tags | grep v1.0.0
# Show what the remote tag points to
git ls-remote --tags origin | grep v1.0.0
# Compare the commits
git log --oneline -1 v1.0.0 # Local tag
git log --oneline -1 origin/v1.0.0 # May not work if not fetchedIf the hashes differ, your local tag points to a different commit than the remote tag. You'll need to decide which version is correct before proceeding.
Fetch remote tags to see what exists:
git fetch --tags
git tag -lThe safest approach when you intentionally want to update a tag is to delete it from the remote first, then push your local version:
# Delete the tag on the remote
git push origin --delete v1.0.0
# Or using the colon syntax
git push origin :refs/tags/v1.0.0
# Now push your local tag
git push origin v1.0.0This is the recommended approach because:
- It makes your intention explicit
- Other collaborators will see the tag was deleted and recreated
- It works even when force push is disabled on the remote
Note: If others have already fetched the old tag, they'll need to delete it locally and fetch again:
git tag -d v1.0.0
git fetch --tagsIf you need to overwrite the remote tag in one command, use force push:
# Force push a specific tag
git push --force origin v1.0.0
# Or with the full ref path for clarity
git push --force origin refs/tags/v1.0.0:refs/tags/v1.0.0
# Alternative: use + prefix to force a specific ref
git push origin +refs/tags/v1.0.0Warning: Force pushing is potentially destructive:
- The remote may have protections that prevent force pushing tags
- Other users may have already pulled the old tag
- Some CI/CD systems may have already used the old tag reference
To force push all tags (use with caution):
git push --force --tags originThis overwrites ALL tags on the remote that differ from your local tags. Only use this if you're certain your local tags are authoritative.
The cleanest solution is often to use a new tag name instead of trying to move an existing tag:
# Delete the local tag you were trying to push
git tag -d v1.0.0
# Create a new tag with a different version
git tag -a v1.0.1 -m "Release v1.0.1"
# Push the new tag
git push origin v1.0.1This approach is recommended when:
- The existing tag has already been used in production
- Other developers may have built software against the old tag
- You're following semantic versioning and should increment anyway
- The remote strictly enforces tag immutability
For pre-release or release candidate tags, consider using suffixes:
git tag -a v1.0.0-rc2 -m "Release candidate 2"
git push origin v1.0.0-rc2If your local tags are out of sync with the remote and you want to accept the remote versions:
# Fetch all tags from remote, overwriting local conflicts
git fetch --tags --force
# Or using the long form
git pull --tags --forceThis will update your local tags to match the remote, which resolves conflicts by accepting the remote's version.
To see which tags differ between local and remote:
# List local tags
git tag -l
# List remote tags
git ls-remote --tags origin
# Find tags that exist locally but not on remote
git tag -l | while read tag; do
git ls-remote --tags origin | grep -q "refs/tags/$tag$" || echo "Local only: $tag"
doneMany Git GUI clients like SourceTree have a "Push all tags" option enabled by default, which can cause this error when you have old local tags that conflict with remote tags.
In SourceTree:
1. When pushing, uncheck "Push all tags to remotes" in the push dialog
2. Or go to Preferences > Git and disable "Push all tags to remotes"
To clean up old local tags that conflict:
# Delete all local tags
git tag -l | xargs git tag -d
# Fetch fresh tags from remote
git fetch --tagsAlternative: only delete specific conflicting tags:
# Find the specific tag causing the issue
git tag -d v1.0.0
# Fetch to get the remote version
git fetch --tagsAfter cleaning up, the push should succeed without tag conflicts.
Some Git hosting platforms (GitHub, GitLab, Bitbucket) allow protecting tags to prevent modification or deletion:
GitHub:
- Repository Settings > Branches & Tags > Tag protection rules
- Protected tags cannot be deleted or force-pushed
GitLab:
- Settings > Repository > Protected tags
- Only users with certain permissions can create matching tags
Bitbucket:
- Repository settings > Branch restrictions
- Can restrict tag creation and modification
If you're blocked by tag protection:
1. Ask a repository administrator to remove the protection temporarily
2. Have an admin delete the tag via the web interface
3. Use a different tag name that isn't protected
4. Request that an admin recreate the tag pointing to the correct commit
Checking tag protection (GitLab API example):
curl --header "PRIVATE-TOKEN: <your-token>" \
"https://gitlab.com/api/v4/projects/<project-id>/protected_tags"After resolving the issue, verify your tags are correct:
# Verify local and remote tags match
git show-ref --tags
git ls-remote --tags origin
# Check the tag points to the expected commit
git log --oneline -1 v1.0.0Best practices to avoid this error in the future:
1. Push tags immediately after creation - don't let them sit locally
git tag -a v1.0.0 -m "Release v1.0.0"
git push origin v1.0.0 # Push right away2. Use annotated tags for releases - they store metadata and are harder to accidentally move
git tag -a v1.0.0 -m "Release v1.0.0" # Annotated (recommended)
git tag v1.0.0 # Lightweight3. Never reuse tag names - if a tag was published, consider it permanent
4. Coordinate with your team - ensure only one person/system creates release tags
5. Disable "Push all tags" in GUI clients unless you specifically need it
### Why Git Treats Tags as Immutable
Tags in Git are designed to be permanent, immutable references to specific commits. Unlike branches which naturally move forward as you commit, tags mark fixed points in history - typically releases, milestones, or important commits.
The "already exists" rejection is Git's way of enforcing this convention. While Git technically allows moving tags with force flags, doing so is discouraged because:
1. Reproducibility: If tag v1.0.0 pointed to commit A yesterday and commit B today, anyone who built from that tag would get different code
2. Security: Mutable tags can be exploited to inject malicious code into builds that reference tags
3. Trust: Users expect tagged releases to be stable and unchanging
### Lightweight vs Annotated Tags
Git supports two types of tags:
# Lightweight tag - just a pointer to a commit
git tag v1.0.0
# Annotated tag - contains tagger info, date, message, can be signed
git tag -a v1.0.0 -m "Release version 1.0.0"Annotated tags are recommended for public releases because they:
- Store who created the tag and when
- Can include release notes in the message
- Can be GPG-signed for verification
- Are treated as first-class objects in Git
### The Branch/Tag Name Collision Issue
Git uses refs (references) to track branches and tags:
- Branches: refs/heads/name
- Tags: refs/tags/name
If you have both a branch and tag named "release", Git may have trouble resolving which you mean:
# Ambiguous - could be branch or tag
git checkout release
# Explicit - definitely the tag
git checkout refs/tags/releaseAvoid using the same name for branches and tags to prevent confusion.
### Handling Tags in CI/CD Pipelines
When CI/CD systems create tags automatically, coordinate to prevent conflicts:
# Example GitHub Actions workflow that creates tags
- name: Create Release Tag
run: |
git config user.name "GitHub Actions"
git config user.email "[email protected]"
# Check if tag exists
if git rev-parse "v${{ env.VERSION }}" >/dev/null 2>&1; then
echo "Tag already exists, skipping"
exit 0
fi
git tag -a "v${{ env.VERSION }}" -m "Release v${{ env.VERSION }}"
git push origin "v${{ env.VERSION }}"### Recovering from Accidental Tag Updates
If you accidentally force-pushed a tag and need to recover the original:
# If someone still has the old tag
git fetch colleague:refs/tags/v1.0.0.backup
git push --force origin refs/tags/v1.0.0.backup:refs/tags/v1.0.0
# If you know the original commit hash
git tag -f v1.0.0 abc123
git push --force origin v1.0.0
# Check reflog for the original tag (if it was local)
git reflog | grep v1.0.0### Tag Signing for Security
For production releases, consider signing tags with GPG:
# Create a signed tag
git tag -s v1.0.0 -m "Signed release v1.0.0"
# Verify a signed tag
git tag -v v1.0.0Signed tags provide cryptographic proof of who created the tag and that it hasn't been tampered with.
warning: BOM detected in file, this may cause issues
UTF-8 Byte Order Mark (BOM) detected in file
fatal: Server does not support --shallow-exclude
Server does not support --shallow-exclude
warning: filtering out blobs larger than limit
Git partial clone filtering large blobs warning
fatal: Server does not support --shallow-since
Server does not support --shallow-since in Git
kex_exchange_identification: Connection closed by remote host
Connection closed by remote host when connecting to Git server