TechEarl

Git: 'failed to push some refs' - How to Fix It

Git refused your push because the remote has commits you do not have. Pull with rebase first, then push. Here is exactly why it happens and how to fix it safely.

Ishan Karunaratne⏱️ 7 min readUpdated
Share thisCopied
How to fix the Git failed to push some refs rejection caused by a non-fast-forward remote

failed to push some refs means the remote branch has commits that your local branch does not. Git will not overwrite that work, so it rejects the push. The one-line fix is to bring those remote commits down first, then push:

bash
git pull --rebase
git push

That replays your local commits on top of whatever was on the remote, and the second push goes through. The rest of this page explains why Git stops you, what "non-fast-forward" actually means, and the one time forcing the push is acceptable.

The full error

When you run git push and the remote has moved ahead of you, Git prints something like this:

text
 ! [rejected]        main -> main (fetch first)
error: failed to push some refs to 'github.com:you/your-repo.git'
hint: Updates were rejected because the remote contains work that you do
hint: not have locally. This is usually caused by another repository pushing to
hint: the same ref. If you want to integrate the remote changes, use 'git pull'
hint: before pushing again.

The important parts are (fetch first) and the hint that the remote "contains work that you do not have locally." That is the whole story. Nothing is broken. Git is protecting commits that are on the server but not in your local copy.

Why does this happen?

A push only succeeds when it is a fast-forward: the remote branch tip is a direct ancestor of the commit you are pushing, so Git can simply slide the branch pointer forward without losing anything.

You get the rejection when both sides have new commits. The classic sequence:

  1. You and a teammate both start from commit A on main.
  2. Your teammate commits B and pushes it. The remote main is now at B.
  3. You commit C locally. Your local main is at C, whose parent is A, not B.
  4. You push. Git sees the remote at B and your branch at C, with no straight line between them. The histories have diverged, so the push is rejected as non-fast-forward.

It also happens to a single person across two machines (commit on the laptop, push, then push older work from the desktop), or after someone rewrote remote history with a force push. The cause is always the same: the remote ref points somewhere your local branch cannot reach. A disciplined branching workflow for a team, where everyone works on short-lived feature branches rather than committing straight to a shared main, cuts down how often two people collide on the same branch tip in the first place.

The fix: pull, then push

Integrate the remote commits into your branch first. I almost always do this with --rebase, which replays your local commits on top of the remote tip and keeps history linear:

bash
git pull --rebase
git push

If your local commits and the remote commits touched different files, the rebase finishes silently and the push succeeds. If they touched the same lines, the rebase pauses on a conflict. That is normal; resolve it, then run git add to mark the file as resolved (this is exactly what the staging area and git add record), and continue:

bash
git add path/to/resolved-file
git rebase --continue

I walk through that conflict workflow in detail in resolving merge conflicts in Git. Once the rebase is clean, push.

Plain git pull (merge) also works

If you do not pass --rebase, git pull does a merge instead. It pulls the remote commits and creates a merge commit that ties the two histories together, then you push:

bash
git pull
git push

This works too. The trade-off is that merge leaves an extra merge commit and a non-linear history, while rebase keeps a straight line. For the difference and when each is the right call, see git merge vs git rebase. Either way, you are doing the same thing: getting the remote commits into your branch before you push.

If git pull prints an error about not knowing how to reconcile divergent branches, your Git is asking you to pick merge or rebase explicitly. That is a separate divergent-branches setup step, and it is a one-time configuration.

reset/revert is not the fix here

A common wrong turn is to reach for git reset to "get rid of" the rejection. That does not address the cause and can throw away your local commit. The rejection is about reconciling diverging history, not undoing it. If you genuinely do want to undo a local commit before pushing, that is a different task covered in undoing the last commit and the reset vs revert comparison. Do not reset just to silence this push error.

When is --force acceptable?

Forcing the push tells Git to overwrite the remote branch with your local one, discarding the remote commits you were just warned about. On a shared branch that is how you delete a teammate's work. So the honest default is: do not force-push a shared branch.

There is one legitimate case. You rewrote your own commits (an interactive rebase to squash, an amend, a fixup) on a branch that is only yours, typically a feature branch backing a pull request. The remote still has the old versions, the histories diverge by design, and a plain pull would re-merge the very commits you just rewrote. Here you do want to replace the remote, and you should use --force-with-lease rather than --force:

bash
git push --force-with-lease

--force-with-lease refuses the push if the remote moved since you last fetched, so it cannot silently clobber a commit someone else pushed while you were rebasing. Plain --force skips that check and overwrites unconditionally. Reach for the lease variant every time. If a force push did wipe a commit you still had locally, the work is usually not gone for good: you can often recover them with git reflog as long as the objects have not been garbage-collected.

ApproachWhat it doesWhen to use
git pull --rebase then git pushReplays your commits on top of remote, linear historyThe default. Diverged history you did not intend to rewrite.
git pull then git pushMerges remote into local, adds a merge commitSame goal, when you prefer merge semantics
git push --force-with-leaseOverwrites remote, but only if it has not moved since your fetchYou rewrote your own commits on a branch that is yours
git push --forceOverwrites remote unconditionallyAlmost never; it can erase work you did not check for

If you protect main (which you should, see protecting your main branch), force pushes to it are blocked at the server anyway, which is exactly the safety net you want.

Other reasons a push fails

failed to push some refs is a family of messages. The "remote contains work you do not have" version is the common one, but the same first line shows up for other causes, each with its own fix:

If the hint in your output mentions "fetch first" or "remote contains work," you are in the case this page covers. New to all of this and want the foundation underneath it? Start at Git for beginners and the explainer on git pull vs git fetch, which is the mechanic behind the fix above.

Sources

Authoritative references this article was fact-checked against.

Tagsfailed to push some refsGitVersion Controlnon-fast-forwardgit pushgit pull

Found this useful? Pass it on.

Copied

Ishan Karunaratne

Tech Architect · Software Engineer · AI/DevOps

Tech architect and software engineer with 20+ years building software, Linux systems, and DevOps infrastructure, and lately working AI into the stack. Currently Chief Technology Officer at a healthcare tech startup, which is where most of these field notes come from.

Keep reading

Related posts