Why doesn't a new Git tag like v1.4.0 reach the remote after git push, and how do you send it?
answer
- push and tag are separate publications
- branches live in one namespace, tags another
- the default refspec covers refs/heads only
- a flag that follows annotated tags outward
- fetch is more generous than push here
basics
~20 sA plain git push sends branch refs only; tags live in refs/tags and are excluded by the default refspec. Push one explicitly with git push origin v1.4.0, or push a branch plus its reachable annotated tags with git push --follow-tags.
solid answer
~40 sCreating a tag is a purely local act. The default push refspec covers `refs/heads/*` — branches — so nothing under `refs/tags/*` travels with it, which is why a release tag looks "missing" to everyone else until you publish it. Three ways to publish: `git push origin v1.4.0` names the single tag, `git push origin --tags` pushes every local tag including lightweight scratch ones, and `git push --follow-tags` pushes the branch plus annotated tags reachable from the commits being pushed — the right default for a release, and settable once as `push.followTags = true`. On the receiving side, a normal `git fetch` auto-follows tags that point into the history it just downloaded, so collaborators pick up the release tag without extra flags. Verify publication with `git ls-remote --tags origin`.
code
bash · 5 linesgit tag -a v1.4.0 -m "Release 1.4.0"
git push origin release/1.4 # branch goes, tag does not
git push origin v1.4.0 # publish just this tag
git push --follow-tags origin release/1.4 # branch + reachable annotated tags
git ls-remote --tags origin # confirm from the remote's point of viewgo deeper
Remember the two-step: creating a tag is local, publishing it is a separate push. Be able to name git push origin <tag> and to check the result with git ls-remote --tags origin.
Explain why the default push refspec covers only refs/heads/*, and contrast --tags with --follow-tags in terms of which tags each selects and why that matters on a shared remote.
Show the operational judgment: standardise push.followTags, tag the exact verified commit, and treat published tags as immutable because fetch never overwrites an existing local tag.
Own the policy question — who is allowed to create release tags, what naming scheme makes them machine-sortable, and how a build system proves an artifact came from the tagged commit rather than a rebuilt one.
## The one-line cause Tags are refs, and `git push` does not push all refs — it pushes the refs its refspec selects. With no arguments, `git push` uses `push.default` (`simple` in modern Git), which pushes the current branch to its upstream branch. That refspec lives entirely inside `refs/heads/*`. A tag you just created lives at `refs/tags/v1.4.0`. It is simply not in the set of things being sent, so the push succeeds, reports the branch update, and quietly leaves the tag behind on your machine. This surprises people because tagging *feels* like the last act of a release. Mechanically it is two separate publications: the commits, and the name you pinned to one of them. ## The three ways to publish a tag **Name it explicitly.** `git push origin v1.4.0` pushes exactly that ref. Git expands the short name to `refs/tags/v1.4.0` when it is unambiguous; if a branch and a tag share a name you can spell the full ref, `git push origin refs/tags/v1.4.0`, and avoid the ambiguity entirely. **Push all tags.** `git push origin --tags` sends everything under `refs/tags/*`. It is blunt: it publishes lightweight tags you created as bookmarks, tags left over from an experiment, and tags pointing at commits that are not reachable from any published branch. On a shared repository that is noise other people cannot easily clean up, because deleting a published tag requires everyone to prune it. **Push the branch and its annotated tags.** `git push --follow-tags` pushes the refs it would normally push, plus annotated tags that are reachable from the commits being pushed. That is almost exactly the release semantics you want: the tag for the release you are shipping goes out, unrelated local tags stay home, and lightweight scratch tags are ignored because `--follow-tags` only considers annotated ones. Make it permanent with `git config --global push.followTags true`. ## How everyone else gets the tag Fetching is more generous than pushing. A plain `git fetch` performs *tag auto-following*: after downloading the requested refs, Git also downloads any tags that point at objects now in the local history. So once the release tag is on the remote and the commit it names is on a fetched branch, collaborators get it with no special flags. `git fetch --tags` additionally fetches tags that are not reachable from the fetched branches; `--no-tags` turns auto-following off. One important asymmetry: fetch does **not** update a tag that already exists locally with a different value. Tags are treated as immutable names. If you move a published tag and re-push it with force, clones that already have the old value keep the old value silently unless someone forces the update. This is the practical reason for the rule "never move a published tag" — the mechanism does not propagate the move, so different machines end up disagreeing about what `v1.4.0` means, and any build reproduced from the tag is no longer reproducible. ## Deleting and correcting If you must retract a tag before anyone depends on it: `git tag -d v1.4.0` locally, then `git push origin --delete v1.4.0` (equivalently, the older colon refspec `git push origin :refs/tags/v1.4.0`). Deletion does not propagate on fetch either — collaborators keep the deleted tag until they run `git fetch --prune --prune-tags` or delete it by hand. Once a tag is genuinely published and consumed, the safe correction is a new tag on the corrected commit, not a redefinition of the old one. ## Where this sits in a release A typical release sequence: cut or update the release branch, build and verify, then tag the **exact commit** that produced the artifact you tested — `git tag -a v1.4.0 -m "Release 1.4.0"` — and publish branch and tag together with `git push --follow-tags origin release/1.4`. Confirm with `git ls-remote --tags origin`, which asks the remote directly instead of trusting local state; that one command catches the whole class of "we tagged it but never shipped the tag" mistakes. ## What an interviewer is checking That you know a tag is a ref rather than a property of a commit, that push and fetch have deliberately different default policies for the tag namespace, and that you reach for `--follow-tags` rather than the shotgun `--tags`. The follow-up is usually about immutability: why re-pointing a released tag is a bad idea even though Git will let you do it locally.
- Why is `git push --follow-tags` usually preferred over `git push --tags` when shipping a release?`--tags` pushes every local tag, including lightweight bookmarks and tags on commits nobody else has, and published tags are awkward to retract. `--follow-tags` pushes only annotated tags reachable from the commits in this push, so exactly the release tag goes out with the release commits and local clutter stays local.
- A colleague already fetched v1.4.0, then you force-move that tag to a different commit and push it. What do they see?Nothing, by default. Fetch will not overwrite a tag that already exists locally, so they keep the old target while you and fresh clones see the new one, and builds "from v1.4.0" stop being reproducible. That silent divergence is why a published tag should be corrected with a new tag, not moved.
- How do you check what tags actually exist on the remote rather than locally?`git ls-remote --tags origin` queries the remote's refs directly without touching your local refs, so it distinguishes "I created the tag" from "the tag is published". Annotated tags additionally show a `^{}` peeled line giving the commit the tag object points at.
saying these in an interview costs you the question
- Thinks git push sends every local ref including tags
- Uses git push --tags routinely on a shared repository
- Believes tags are attached to commits, not separate refs
- Moves and force-pushes a released tag to fix it
- Assumes collaborators must run git fetch --tags to see any tag