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:
git pull --rebase
git pushThat 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:
! [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:
- You and a teammate both start from commit
Aonmain. - Your teammate commits
Band pushes it. The remotemainis now atB. - You commit
Clocally. Your localmainis atC, whose parent isA, notB. - You push. Git sees the remote at
Band your branch atC, 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:
git pull --rebase
git pushIf 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:
git add path/to/resolved-file
git rebase --continueI 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:
git pull
git pushThis 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:
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.
| Approach | What it does | When to use |
|---|---|---|
git pull --rebase then git push | Replays your commits on top of remote, linear history | The default. Diverged history you did not intend to rewrite. |
git pull then git push | Merges remote into local, adds a merge commit | Same goal, when you prefer merge semantics |
git push --force-with-lease | Overwrites remote, but only if it has not moved since your fetch | You rewrote your own commits on a branch that is yours |
git push --force | Overwrites remote unconditionally | Almost 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:
- The branch has no upstream set yet, so Git does not know where to push: see setting an upstream branch.
- A server-side hook rejected the push (a CI check, a commit-message rule): see pre-receive hook declined.
- A commit contains a file over GitHub's size limit: see the file size limit error.
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.
- git-push - official Git documentationgit-scm.com
- Pro Git - Git Branching: Rebasinggit-scm.com
- Dealing with non-fast-forward errors - GitHub Docsdocs.github.com





