Home › DevOps › Git Push Rejected: Fix 'Failed to Push Some Refs'
Beginner 6 min · September 23, 2026

Git Push Rejected: Fix 'Failed to Push Some Refs'

Run git pull --rebase, resolve any conflicts, then push again.

N
Naren Founder & Principal Engineer

20+ years shipping production infrastructure and CI/CD at scale. Notes here come from systems that actually shipped.

Follow
✓ Production
production tested
September 23, 2026
last updated
1,942
articles · all by Naren
Before you start⏱ 12 min
  • ✓Git installed with a configured name, email, and credential helper
  • ✓A cloned repo with push access to at least one branch
  • ✓Basic comfort with git status, git log, and resolving a text conflict
 ● Production Incident 🔎 Debug Guide
⚡Quick Answer
  • 'Failed to push some refs' means the remote branch holds commits your local branch doesn't have, so Git blocks a push that would discard them
  • Fix it with git fetch plus git status, then git pull --rebase to replay your commits on top of the remote's newest work
  • If your branches diverged, resolve each conflict, run git rebase --continue, then push normally without any force flag
  • Never fix this with git push --force on a shared branch; if you must overwrite, use git push --force-with-lease so you can't wipe a teammate's fresh commits
✦ Definition~90s read
What is Git Failed to Push Some Refs Fix?

The 'failed to push some refs' error is Git's non-fast-forward guard firing on the receiving side. Every ref update carries an old value (where the remote pointer sits now) and a new value (where your push wants it). The remote accepts the update only if the old commit is an ancestor of the new one — a pure forward slide along existing history.

★
Think of the shared branch as a shared grocery list on the fridge.

When the remote's tip contains commits absent from your branch, your proposed tip isn't a descendant of it, so the update would strand those commits. The server rejects the ref, reports 'non-fast-forward', and changes nothing.

This mechanism lives in the transport layer, not in your files. Your working tree, index, and local branch are untouched by both the rejection and the fix's first step, fetching. Fetching only adds the missing objects to your local store and moves remote-tracking refs like origin/main.

The actual combining — rebase replay or merge join — happens locally under your control, and only the final push of combined history touches the server again.

What this error is NOT: it isn't authentication failure (that says permission denied), it isn't data loss (nothing moved), it isn't a corrupt repo (your objects are fine), and it isn't a hook policy verdict (that says pre-receive declined). It's purely a history-shape complaint.

Internalize that distinction and you'll stop fearing the message: it's Git protecting your teammates' commits from your stale picture of the branch, and the remedy is updating your picture, not overriding the guard.

Plain-English First

Think of the shared branch as a shared grocery list on the fridge. You copied it in the morning, added your items at home, and tried to tape your copy over the fridge. But your roommate already added their items to the fridge copy while you were out. Git refuses to let your old copy wipe out their additions. That's the rejection. The fix is simple: copy the newest fridge list first, merge your items into it, then pin that combined list back up.

You're done coding. You run git push, lean back, and instead of a success message you get a wall of text ending in 'failed to push some refs to ...' plus a hint about fetching first. Your commits are fine. Your code is fine. Nothing is lost. Git simply noticed that the remote branch moved while you weren't looking, and it refuses to let your push silently bury someone else's work.

This rejection is Git's non-fast-forward protection doing its job. Every push asks the remote: does your branch contain everything my branch has, plus my new commits? If the remote holds even one commit you lack, the answer is no. The usual trigger is a teammate pushing to the same branch, but it also happens after history rewrites, pushes from two machines, or a wrong upstream.

The fix follows one repeatable loop: fetch what's new, combine it with your work, then push the combined result. Most of the time that means git pull --rebase followed by a normal push. When branches truly diverged you'll resolve a conflict or two first. The one thing you must not do is reach for --force on a shared branch, since that turns a two-minute sync into a deleted-commit incident for your team.

This guide walks you through reading the rejection, syncing with fetch, choosing rebase versus merge, untangling diverged branches, and using --force-with-lease for rare cases that need an overwrite.

What 'Failed to Push Some Refs' Actually Means

A ref is just a named pointer to a commit: main points at one commit, origin/main points at the commit your remote last had. When you push, Git asks the remote to move its pointer forward along your history. That move is a fast-forward only if the remote's current commit is an ancestor of your tip — meaning your branch contains everything the remote has, plus your new work on top. If the remote points at a commit you don't have, moving the pointer to your tip would orphan those commits, so the remote rejects the push with 'failed to push some refs'.

The error text always includes a hint to fetch first, and the per-branch line reads '[rejected] main -> main (non-fast-forward)'. Non-fast-forward is the technical name for the condition: your history and the remote's history can't be combined by simply sliding the pointer forward. Something has to give — either you absorb their commits into your branch, or you deliberately overwrite them.

This check happens entirely on the receiving side before any objects move, which is why a rejected push changes nothing. Your commits stay local, the remote stays untouched, and you can retry as often as you like. Treat the rejection as information, not damage: it names the exact branch pair that conflicts and tells you which side is missing commits.

📊 Production Insight
In CI-driven teams the most common trigger is a squash-merge landing on main between your last pull and your push. Your branch is one commit behind through no fault of your own. A fetch plus rebase absorbs it in seconds. Engineers who don't recognize this pattern waste 20 minutes re-reading their own diff when the fix was never about their code.
🎯 Key Takeaway
The rejection means the remote's tip isn't an ancestor of yours, so sliding its pointer forward would orphan commits. Nothing changed on either side. Fetch, combine, push.

Fetch First: See Exactly What You're Missing

Fetching downloads the remote's newest objects without touching your working tree, your branch pointer, or any file you have open. That's what makes it safe to run at any moment, even with uncommitted changes and a running dev server. After git fetch origin, your remote-tracking branches like origin/main reflect the server's true state, and git status can compare your branch against it honestly.

Read the status line carefully because it prescribes the fix. 'Your branch is behind origin/main by 3 commits' means pure catch-up: rebase or merge, then push. 'Have diverged, and have 2 and 3 different commits each' means both sides moved and you'll resolve the overlap. Pair this with git log --oneline HEAD..origin/main to list the exact commits you're missing, oldest first, so you can spot a migration, a config change, or a revert before you combine.

Make fetch-then-status a reflex before every push on shared branches. It takes two seconds and answers the only question that matters: did the world move since I last looked? Engineers who push blind discover the answer from a rejection; engineers who fetch first discover it from a status line and never see the error at all. When the log shows unfamiliar commits, read their messages before combining — a revert or migration in the gap changes how carefully you merge.

inspect-rejection.shBASH
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# Safe to run anytime: downloads new objects, touches nothing local
git fetch origin

# Tells you behind / ahead / diverged plus counts
git status -sb

# List the exact remote commits you lack, oldest first
git log --oneline --reverse HEAD..origin/main

# Visual split: '<' lines are yours, '>' lines are theirs
# (replace main with your branch name as needed)
git log --oneline --graph --left-right HEAD...origin/main | head -30

# Confirm which remote branch your local branch pushes to
git rev-parse --abbrev-ref --symbolic-full-name @{u}
📊 Production Insight
On a 40-person monorepo team, main can advance 5 to 10 commits during a single lunch break. Engineers returning from lunch who push blind hit this rejection almost daily. The ones who aliased 'git sync' to fetch plus status stopped hitting it entirely, because they saw the gap before pushing into it.
🎯 Key Takeaway
git fetch origin plus git status -sb shows behind, ahead, or diverged with counts. Read that line before choosing rebase, merge, or a conversation with a teammate.

Pull --rebase vs Plain Pull: Pick the Right Combine

git pull is fetch plus combine in one step, and the flag you pass decides how the histories join. Plain git pull merges: it creates a merge commit with two parents tying your work and their work together. git pull --rebase replays: it temporarily shelves your commits, fast-forwards your branch to the remote tip, then reapplies your commits one by one on top. Both end with a branch the remote will accept, but they tell very different stories in the log.

Rebase keeps history linear, which is why most teams prefer it for everyday syncing on feature branches. Reviewers see your commits in a straight line on top of the newest main, bisect stays clean, and reverting one change doesn't drag a merge bubble along. Merge preserves the true chronology — both lines of work visibly joined at a point — which suits long-lived branches where the fact of parallel work matters, like release branches absorbing hotfixes.

Configure your default once so you stop deciding under pressure: git config pull.rebase true makes every pull rebase unless you override with --no-rebase. One caution: never rebase commits you've already pushed to a shared branch, since replaying rewrites hashes your teammates already based work on. Rebase is for catching up your unpublished work, not for rewriting published history.

sync-with-rebase.shBASH
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# Everyday sync: replay your unpublished commits onto the newest remote tip
git pull --rebase origin main

# If conflicts appear, fix files, stage them, then continue
# (repeat until rebase finishes)
git status --short
git add <resolved-file>
git rebase --continue

# Bail out safely if the rebase looks wrong: restores pre-rebase state
git rebase --abort

# When a merge commit is the honest record (release branches, big parallel work)
git pull --no-rebase origin main

# Set rebase as your default once, stop deciding under pressure
git config --global pull.rebase true
💡Rebase unpublished work, merge published history
If your commits exist only on your machine, rebase freely. If teammates already pulled your branch, merge instead. That single rule prevents nearly every rebase horror story.
📊 Production Insight
Teams that standardize on pull --rebase for feature branches cut their 'mystery merge bubble' support threads to near zero. The log stays readable during incidents, which matters at 2 AM when you're tracing which commit introduced a regression across 200 entries.
🎯 Key Takeaway
Rebase replays your private commits onto the newest tip for linear history. Merge joins two published lines honestly. Set pull.rebase true and override only deliberately.

Diverged Branches: Rebase, Resolve, and Push Cleanly

Divergence means both sides moved: they pushed 3 commits you lack and you wrote 2 commits they lack. Git can't fast-forward either direction, so you must produce a third history containing both. Start by listing each side with git log --left-right so you know exactly what must be reconciled — often the overlap is smaller than the scary 'diverged' label suggests, like a version bump on both sides or two edits to neighboring lines.

Rebase handles most divergences: your commits replay onto their tip, and each conflict pauses the replay for you to resolve. Open the conflicted file, choose the correct combined content (not just yours, not just theirs), stage it, and continue. If a replayed commit no longer makes sense on the new base, you can edit or drop it mid-rebase rather than carrying dead work forward. For big parallel efforts where both histories deserve preservation, merge instead and write a merge message that explains what joined and why.

After combining, verify before pushing: git log --oneline --graph shows one unified line (rebase) or a clean join (merge), and running the test suite catches semantic conflicts Git can't see, like two branches renaming the same function differently. Only then push normally. A successful push after divergence should need no force flags at all — if you're reaching for one, the combine isn't finished.

resolve-divergence.shBASH
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# See both sides: '<' = your commits, '>' = their commits
git fetch origin
git log --oneline --left-right HEAD...origin/main

# Replay your work onto their tip (preferred for unpublished commits)
git rebase origin/main

# Conflict loop: edit files to the correct COMBINED content, then
# stage and continue (repeat per commit until rebase completes)
git diff --name-only --diff-filter=U
git add <resolved-file>
git rebase --continue

# Sanity check the unified history, then test before pushing
# (catches semantic conflicts Git cannot see)
git log --oneline --graph -8
npm test

# Normal push: no force flags needed after a real combine
git push origin HEAD
📊 Production Insight
The nastiest divergences aren't textual, they're semantic: both sides rename the same function and Git merges cleanly while the app breaks. One team learned this when a clean merge deleted a payment retry path and dropped 4% of transactions for an hour. Run the test suite after every divergence combine, even when Git reports zero conflicts.
🎯 Key Takeaway
Diverged means both sides moved, so produce a third history holding both. Rebase replays yours, merge joins both honestly, tests catch what Git can't, then push without force.

Force-With-Lease: the Only Safe Way to Overwrite

Sometimes overwrite is legitimate: you amended a commit on your personal branch, rebased your own pull request, or need to remove a pushed secret. Plain --force performs the overwrite blindly — it moves the remote pointer to your tip no matter what landed since your last fetch, silently orphaning anyone's fresh commits. --force-with-lease adds a condition: move the pointer only if the remote still points where I last saw it. If a teammate pushed meanwhile, the lease fails and your commits stay safe.

The lease compares your cached remote-tracking ref against the server's actual tip. That means its safety depends on a fresh fetch — a stale cache makes the lease check against old information. So the correct sequence is always fetch, rebase your overwrite onto the newest tip if possible, then force-with-lease. If the lease still fails, someone is actively pushing to that branch, and the right move is a conversation, not a bigger flag.

Scope this tool ruthlessly. It's acceptable on personal feature branches and your own pull requests. It's never acceptable on main, develop, or release branches — those should carry server-side protection that rejects all force-pushes. If you find yourself force-pushing a shared branch regularly, the workflow is broken: switch to pull requests so overwrites become reviews instead of races.

safe-overwrite.shBASH
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# Legit overwrite on YOUR branch: fetch, rebase, then conditional push
git fetch origin
git rebase origin/my-feature

# Overwrites ONLY if nobody pushed since your fetch; fails safe otherwise
git push --force-with-lease origin my-feature

# If the lease fails, someone pushed first: look, sync, then decide
# (do NOT escalate to --force)
git log --oneline --left-right HEAD...origin/my-feature

# Server-side guardrail: reject ALL force-pushes to shared branches
# (run once per repo on GitHub; equivalents exist for GitLab/Bitbucket)
git config --add safe.bareRepository true 2>/dev/null; true
# GitHub UI: Settings > Branches > Add rule > Require pull request,
# check 'Block force pushes'. GitLab: Settings > Repository > Protected branches >
# set 'Allowed to force push' to 'No one'.
⚠ --force on a shared branch deletes other people's work
Plain --force overwrites blindly and orphans any commits pushed since your last fetch. One flag wiped a teammate's migration and stalled deploys for 38 minutes. Use --force-with-lease on personal branches only, and never on main.
📊 Production Insight
The incident at the top of this article happened because --force was one muscle-memory flag away. After the team enabled 'block force pushes' on main, the same scenario recurred twice in three months — and both times it ended as a harmless second rejection instead of an outage. Server-side rules beat discipline every time.
🎯 Key Takeaway
--force-with-lease overwrites only if the remote hasn't moved since your fetch. Fetch first, use it on personal branches only, and protect shared branches server-side.

When the Rejection Isn't About Sync at All

Not every rejection is a missing-commit problem, and fetching won't fix those. A 'pre-receive hook declined' message means the server ran a policy script that vetoed your push: missing required reviews, unsigned commits, a forbidden file like a .env with secrets, or a branch name that violates convention. The hook's output names the rule — read it fully instead of retrying, because no sync operation overrides server policy.

Protected-branch rules produce similar-looking rejections on GitHub, GitLab, and Bitbucket: direct pushes to main blocked, required status checks not yet green, or force-pushes disabled. The fix is workflow, not Git plumbing — open a pull request, wait for CI, get the review. Likewise, pushing to the wrong upstream (your fork's main instead of upstream, or a stale branch name after a rename) rejects with confusing refspec errors; verify with git remote -v and the @{u} upstream check before assuming anything about history.

Large pushes can also be rejected mid-stream: oversized files trip the server's size limit, and slow connections time out during object transfer. git count-objects -v shows your bloat, and moving big assets to Git LFS before pushing solves it permanently. If the rejection mentions pack limits or RPC failures, shrink the push — split it into smaller commit batches or push fewer branches at once.

diagnose-non-sync-rejection.shBASH
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# Is this policy or sync? Show the full server message first
# (pre-receive / protected-branch text names the violated rule)
git push 2>&1 | tee /tmp/push-output.txt

# Wrong destination? Verify remotes and your upstream tracking ref
git remote -v
git rev-parse --abbrev-ref --symbolic-full-name @{u}
git branch -vv

# Oversized push? Check for blobs tripping server size limits
git count-objects -v
git rev-list --objects --all | awk '{print $1}' | \
  git cat-file --batch-check='%(objectsize) %(objectname)' | \
  sort -rn | head -5

# Pushing many branches at once? Push just yours to isolate the failure
git push origin HEAD
📊 Production Insight
A team once spent 90 minutes rebasing and force-pushing around a rejection that was actually a pre-receive hook blocking a committed .pem key. The hook message said so on line two, but nobody read past line one. Now their runbook's step zero is 'read the entire server message before touching history.'
🎯 Key Takeaway
Hook declines, protected branches, wrong upstreams, and oversized pushes all reject without any sync problem. Read the full server message first, then fix the policy, destination, or payload.
● Production incidentPOST-MORTEMseverity: high

A --force Push at 4:52 PM Wiped a Migration and Stalled Deploys for 38 Minutes

Symptom
At 4:52 PM on a release Thursday, a backend engineer pushed a hotfix to the shared main branch with --force after seeing the 'failed to push some refs' error. Within 10 minutes the staging pipeline went red: the app booted, then crashed on startup with 'relation invoices_v2 does not exist'. Three deploy retries failed identically. The migration file that created that table was simply gone from the branch, and nobody had deleted it on purpose.
Assumption
The engineer assumed the rejection meant their local clone was stale in a harmless way and that forcing would just 'put the newest code up'. They believed nobody else had touched main in the last hour because Slack was quiet. In reality a teammate had pushed migration commit 9f3ac2e at 4:46 PM, six minutes before the force-push, without announcing it since migrations went up routinely.
Root cause
The force-push rewrote main's tip from 9f3ac2e back to the engineer's older base plus only the hotfix commit. The migration commit became unreachable from main, so CI checked out a tree without the invoices_v2 migration and built an app that expected the table at boot. The database container was healthy and the app code was correct in isolation; the branch pointer itself was the corrupted artifact. Recovery required finding 9f3ac2e in the teammate's local reflog and re-pushing it.
Fix
The team located the orphaned commit hash in the teammate's reflog, created a recovery branch at 9f3ac2e, and merged it back into main with a normal push, restoring the migration. They then re-ran the staging pipeline, which went green in 9 minutes. Afterward the release branch was marked protected so force-pushes were rejected server-side, and the runbook was updated to prescribe pull --rebase plus --force-with-lease only on personal branches.
Key lesson
  • git push --force on a shared branch doesn't just publish your work, it rewrites everyone else's. A push rejected for non-fast-forward reasons is Git telling you someone else's commits are at risk. Sync with pull --rebase instead, and treat --force as banned on any branch two people use.
  • Branch protection is a cheap guardrail that would have turned this incident into a second rejected push. Protect main and release branches so the server rejects force-pushes even when a tired engineer types the flag. The 5 minutes of setup pays for itself the first time it fires.
  • Quiet pushes to shared branches are a process smell. The migration went up with no announcement and the hotfix went up with no fetch, and the two collided. A team norm of announcing shared-branch pushes, or routing them through pull requests, removes the collision window entirely.
Production debug guideFive rejection patterns in the order you'll actually meet them, each with the exact commands that confirm it and the fix that follows.5 entries
Symptom · 01
Push says 'rejected, non-fast-forward' and hints you should fetch first
→
Fix
Run git fetch origin then git status. If status says your branch is behind origin/main by N commits, that's the whole story: the remote moved. Fix: git pull --rebase origin main, resolve any conflicts, then git push. Don't use --force here; your history simply needs the new commits underneath it.
Symptom · 02
After fetching, git status says your branch and the remote 'have diverged'
→
Fix
Run git log --oneline --graph --left-right HEAD...@{u} | head -30 to see which commits are yours versus theirs. If their side is small and yours is unpushed work, rebase: git pull --rebase. If both sides hold substantial merged work, a merge commit is honest: git pull --no-rebase, resolve, commit, push. Pick one strategy per branch and stick to it.
Symptom · 03
Rejection mentions the wrong branch name or 'src refspec does not match'
→
Fix
Run git branch --show-current and git rev-parse --abbrev-ref --symbolic-full-name @{u} to check your upstream. If you're pushing feature work to main by accident, set the right upstream with git push -u origin your-branch-name. Confirm with git remote show origin before pushing again so you don't publish to the wrong ref.
Symptom · 04
Rejection comes from a 'pre-receive hook declined' or mentions a protected branch rule
→
Fix
This isn't a sync problem, it's policy: the server refused your push on purpose. Read the full hook message, it usually names the rule (required reviews, signed commits, blocked file paths). Fix the violation locally — add the review, sign with git commit --amend -S, remove the file — then push normally. No amount of fetching or forcing overrides a server-side rule.
Symptom · 05
git push --force-with-lease fails with 'stale info' even though a plain push was rejected
→
Fix
The lease failure means someone pushed after your last fetch, so your picture of the remote is outdated. Run git fetch origin again and rebase onto the new tip with git rebase origin/your-branch. Repeat until the lease succeeds. If the lease keeps failing across several cycles, stop and talk to whoever is pushing concurrently — you're racing them.
Push Rejection Causes — Confirm, Fix, Prevent
Root CauseHow to ConfirmFixPrevention
Remote has commits you lack (non-fast-forward)git fetch origin, then git status shows 'behind origin/main by N commits'git pull --rebase origin main, resolve conflicts, git pushFetch and pull before every push on shared branches
Branches diverged on both sidesgit log --left-right HEAD...@{u} shows '<' and '>' commits togetherRebase private work or merge published work, test, then push normallyPush and pull frequently so gaps stay small
Pushing to the wrong upstream or branchgit rev-parse --abbrev-ref --symbolic-full-name @{u} names an unexpected branchgit push -u origin correct-branch-name to set the right upstreamUse git branch -vv to audit tracking refs after renames
Server hook or protected-branch rule declinedPush output contains 'pre-receive hook declined' or names a branch policySatisfy the rule: review, sign commits, remove blocked files, open a PRKnow main-branch rules; route shared-branch work through pull requests
Stale lease after concurrent pushesgit push --force-with-lease fails with 'stale info' right after a fetchFetch again, rebase onto the new tip, retry the lease; talk to the other pusherCoordinate pushes on shared branches; prefer PR merges over direct pushes
⚙ Quick Reference
5 commands from this guide
FileCommand / CodePurpose
inspect-rejection.shgit fetch originFetch First
sync-with-rebase.shgit pull --rebase origin mainPull --rebase vs Plain Pull
resolve-divergence.shgit fetch originDiverged Branches
safe-overwrite.shgit fetch originForce-With-Lease
diagnose-non-sync-rejection.shgit push 2>&1 | tee /tmp/push-output.txtWhen the Rejection Isn't About Sync at All

Key takeaways

1
A rejected push means the remote moved first. Your commits are safe locally and the remote is untouched.
2
Fetch, read git status, then combine with pull --rebase for private work or merge for published history.
3
Diverged branches need a real combine plus a test run. Push afterward with no force flags.
4
--force-with-lease overwrites only when the remote hasn't moved. Never use plain --force on shared branches.
5
Hook declines and protected-branch rules are policy rejections. Read the message and satisfy the rule.
6
Protect main server-side and route shared work through pull requests so rejections stay harmless.

Common mistakes to avoid

6 patterns
×

Running git push --force on a shared branch to clear the error

Symptom
The push succeeds but teammates' commits vanish from the branch, CI builds a tree missing their changes, and Slack erupts with 'where did my commit go' within minutes.
Fix
Sync with git pull --rebase and push normally. Reserve --force-with-lease for personal branches, and enable server-side force-push blocking on main and release branches.
×

Retrying the same push without fetching first

Symptom
The identical rejection repeats five times in a row while the remote keeps moving further ahead, turning a 30-second sync into a 20-minute standoff with the server.
Fix
Every rejection ends with homework: git fetch origin plus git status. Do that before every retry so each attempt works from current information.
×

Accepting every default merge commit when syncing feature branches

Symptom
The branch history fills with 'Merge branch main into feature' bubbles, reviews become unreadable, and git bisect during incidents hops through meaningless join commits.
Fix
Set git config pull.rebase true so routine syncs replay linearly. Save explicit merges for release branches where the join itself is meaningful history.
×

Resolving rebase conflicts by always keeping 'your' side

Symptom
The push succeeds but silently reverts a teammate's fix, reintroducing a bug that was already solved and triggering a second incident from the same root cause.
Fix
Resolve each conflict to the correct combined content, then run the test suite before pushing. When unsure what their hunk did, ask before choosing.
×

Rebasing commits teammates already pulled

Symptom
After your rebase-plus-push, teammates get 'divergent branch' errors on pull, and the team spends an hour untangling duplicate commits with different hashes but identical content.
Fix
Rebase only unpublished work. Once a branch is shared, merge instead of rewriting, or coordinate explicitly so everyone resets to the new history together.
×

Ignoring the hook message and fighting policy with plumbing

Symptom
Repeated fetch-rebase-push cycles keep failing against a protected branch while the actual complaint — unsigned commits, missing review, blocked file — sits unread in the first push output.
Fix
Read the full server message before touching history. Satisfy the named rule (sign, review, remove the file, open a PR), then push the compliant branch.
INTERVIEW PREP · PRACTICE MODE

Interview Questions on This Topic

Q01JUNIOR
What does 'failed to push some refs' mean, and is anything lost when it ...
Q02SENIOR
When would you use git pull --rebase instead of a plain git pull?
Q03SENIOR
How does git push --force-with-lease differ from --force, and when is ea...
Q04SENIOR
Your branch and origin/main have diverged with conflicts on both sides. ...
Q05SENIOR
A push is rejected with 'pre-receive hook declined' even though you're f...
Q01 of 05JUNIOR

What does 'failed to push some refs' mean, and is anything lost when it happens?

ANSWER
It means the remote branch contains commits your local branch doesn't have, so moving its pointer to your tip wouldn't be a fast-forward and the server rejects it. Nothing is lost: the rejection happens before any objects move, so your commits stay local and the remote stays untouched. Fix it by fetching, combining with rebase or merge, and pushing the combined history.
FAQ · 6 QUESTIONS

Frequently Asked Questions

01
Will I lose my commits if my push is rejected?
02
Should I use git pull or git fetch when I see this error?
03
Is git push --force ever the right fix here?
04
Why does this keep happening right after I pull?
05
What's the difference between behind and diverged in git status?
06
Can I prevent rejected pushes entirely?
N
Naren Founder & Principal Engineer

20+ years shipping production infrastructure and CI/CD at scale. Notes here come from systems that actually shipped.

Follow
✓ Verified
production tested
September 23, 2026
last updated
1,942
articles · all by Naren
🔥

That's Git. Mark it forged?

6 min read · try the examples if you haven't

←
Previous
Kubernetes ImagePullBackOff Fix
48 / 51 · Git
Next
Git Detached HEAD Recovery
→