The Hook (The "Byte-Sized" Intro)
"Error: failed to push — non-fast-forward." The first time you see this, it feels like Git is broken. It's not. Git is protecting your team. Someone pushed while you were working, and Git is saying: "Hold on — if I let you push, you'd overwrite their work." The fix is almost always the same: pull, resolve if needed, push again. Learn this flow once and push errors become a 30-second detour.
📖 What are Push Errors?
Push errors occur when Git can't fast-forward the remote branch to include your commits. This happens when the remote has commits you don't have locally — meaning you need to integrate them first.
Conceptual Clarity
Why pushes get rejected:
| Scenario | What Happened | Error |
|---|---|---|
| Teammate pushed first | Remote is ahead of your local branch | ! [rejected] non-fast-forward |
| You amended a pushed commit | Local history diverged from remote | ! [rejected] non-fast-forward |
| Branch is protected | Remote rules prevent direct pushes | ! [remote rejected] |
| No upstream set | Git doesn't know where to push | fatal: no upstream configured |
The core issue: Your local branch and the remote branch have diverged. Git refuses to overwrite the remote's newer commits.
Remote: A - B - C - D (teammate pushed D)
Local: A - B - C - E (you committed E)
↑ divergence
Real-Life Analogy
Imagine two people editing the same Google Doc simultaneously. You try to save, but Google says "someone else already saved a newer version." You must pull their changes, merge your edits with theirs, and then save. Git works the same way.
Visual Architecture
Why It Matters
- Data safety: Push rejection prevents you from overwriting teammates' work.
- Common occurrence: This happens constantly in active teams — it's not a bug, it's a feature.
- Fast fix: The pull → resolve → push flow resolves 99% of push errors.
- Force push danger: Using
--forcebypasses the safety check and can destroy work.
Code
# ─── The Error ───
git push
# Output:
# ! [rejected] main -> main (non-fast-forward)
# hint: Updates were rejected because the remote contains work
# hint: that you do not have locally.
# ─── Fix 1: Pull and merge (standard) ───
git pull
# Merges remote changes into your branch
# Resolve conflicts if any, then:
git push
# ─── Fix 2: Pull with rebase (cleaner history) ───
git pull --rebase
# Replays your commits on top of remote's latest
# If conflicts occur during rebase:
git add resolved-file.js
git rebase --continue
git push
# ─── Fix 3: No upstream set ───
# Error: fatal: The current branch feature has no upstream branch
git push -u origin feature/login
# Sets upstream and pushes
# ─── Fix 4: Protected branch (CI/admin rules) ───
# Error: remote: error: GH006: Protected branch update failed
# Solution: Push to a feature branch and open a Pull Request instead
git push origin feature/login
# Then create a PR on GitHub/GitLab
# ─── LAST RESORT: Force push (DANGEROUS) ───
git push --force-with-lease
# Only use after amending/rebasing YOUR OWN unpushed work
# --force-with-lease is safer: it fails if remote has unexpected changesPush Error Fixes at a Glance
| Error | Cause | Fix |
|---|---|---|
non-fast-forward | Remote has new commits | git pull then git push |
no upstream configured | Tracking not set | git push -u origin <branch> |
Protected branch | Branch rules | Push to feature branch, open PR |
Permission denied | No write access | Verify SSH key / HTTPS token |
Repository not found | Wrong URL / access | Check git remote -v, verify permissions |
Key Takeaways
- Push rejections mean the remote has commits you don't have — pull first, then push.
- Use
git pull --rebasefor a cleaner resolution without extra merge commits. - Never use
--forceon shared branches. Use--force-with-leaseonly when necessary. - "Protected branch" errors mean you should use a pull request workflow.
Interview Prep
-
Q: What causes a "non-fast-forward" push error? A: The remote branch has commits that your local branch doesn't have. Git refuses to push because it would overwrite those commits. The fix is to pull (or fetch + merge/rebase) to integrate the remote changes, then push again.
-
Q: What is the difference between
--forceand--force-with-lease? A:--forceunconditionally overwrites the remote branch, including any commits pushed by others.--force-with-leasechecks that the remote branch is at the commit you expected; if someone else pushed, it fails. This prevents accidentally losing someone else's work. -
Q: How do you handle a push rejection on a protected branch? A: Protected branches don't allow direct pushes. You push your changes to a feature branch (
git push origin feature/my-change), then create a pull request to merge into the protected branch after code review and CI checks pass.