Git — push rejected (non-fast-forward)
Diagnose a rejected Git push by comparing local and remote history, rebasing or merging deliberately, and avoiding destructive force-pushes on shared branches.
Symptoms
- ! [rejected] main -> main (non-fast-forward)
- error: failed to push some refs to 'origin'
- hint: Updates were rejected because the remote contains work that you do not have locally
- The push worked earlier but fails after a teammate merged or rebased
Possible causes
- The remote branch has commits that are not in the local branch
- A local rebase or amend rewrote commits that were already pushed
- The local branch tracks a different remote branch than expected
- A protected branch requires a pull request or linear history
- Two clones pushed to the same branch and one is behind
Diagnosis steps
Fetch and compare the two histories
git fetch origin && git status -sb && git log --oneline --left-right HEAD...origin/mainInterpretation
Commits on the left are local-only; commits on the right are remote-only. If both sides have commits, the histories diverged.
Inspect the remote-only commits before integrating
git log --oneline --stat HEAD..origin/mainInterpretation
Review what changed remotely. Blindly rebasing can create conflicts or duplicate work that was already fixed.
Confirm the branch and remote
git branch -vv && git remote -vInterpretation
A wrong upstream or a branch with the same name in another remote explains a push to an unexpected place.
Root cause
The remote branch contains commits that are not ancestors of the local branch, so Git refuses to overwrite them without an explicit force. The local and remote histories diverged.
Fix
Integrate the remote commits with git pull --rebase (or a merge), resolve conflicts, rerun tests, and push. Do not force-push a shared branch. If a force is truly required after coordination, use --force-with-lease, never a bare --force.
Risk note
Force-pushing rewrites remote history and can discard teammates' commits. It also breaks open pull requests and CI references. Treat it as a destructive, coordinated operation.
Verify the fix
After rebasing or merging, confirm git status is clean, the log contains both local and remote work, tests pass, and the push succeeds without force. Verify the remote branch contains the expected commit.
The remote is not behind; the histories diverged
A non-fast-forward rejection means the push would not be a simple extension of the remote branch. Either the remote has new commits, or the local history was rewritten. The first step is to fetch and look at both sides. Do not run git push --force to make the message disappear.
git fetch origin
git log --oneline --graph --left-right HEAD...origin/main
The left side shows commits only you have; the right side shows commits only the remote has. If the right side is non-empty, integrate it before pushing.
Rebase or merge deliberately
Rebase local commits on top of the remote branch for a linear history:
git pull --rebase origin main
# resolve conflicts, then
git rebase --continue
npm test
git push origin HEAD:main
Merge instead when the branch is shared or the history should preserve the integration point:
git pull --no-rebase origin main
git push origin HEAD:main
Both paths require resolving conflicts and rerunning tests. A rebase can change commit hashes; if the branch is already shared, coordinate before rewriting it.
Never force a shared branch casually
git push --force overwrites the remote branch with the local one and can delete commits that others depend on. If a force is unavoidable, use --force-with-lease, which refuses the push if the remote moved since your last fetch, and announce the rewrite. For protected branches, use a pull request rather than bypassing the protection.
Check the upstream before retrying
A surprising rejection is sometimes a branch mismatch: local main tracks origin/develop, or the push targets a fork instead of the shared repository. git branch -vv and git remote -v make that visible. Fix the upstream or specify the remote and branch explicitly rather than guessing.