How to Fix GitHub Push Rejected Non-Fast-Forward: A Complete CI/CD Guide
Few things in a developer’s day are as immediately frustrating as finishing a complex feature, attempting to push your code to GitHub, and being met with a wall of red text.
If you are reading this, you have likely just encountered the dreaded terminal output:
! [rejected] main -> main (non-fast-forward)
error: failed to push some refs to 'github.com:user/repo.git'
hint: Updates were rejected because the tip of your current branch is behind
hint: its remote counterpart. Integrate the remote changes (e.g.
hint: 'git pull ...') before pushing again.
This error is a rite of passage for software engineers, but it can completely halt your CI/CD pipeline if you don’t understand what is happening under the hood. In this comprehensive guide, we will break down exactly how to fix github push rejected non fast forward, covering everything from basic root causes to complex edge cases involving detached HEADs and CI/CD automation.
Let’s dive into the terminal and get your code pushed.
Understanding the Root Cause of the Non-Fast-Forward Error
Before we start running commands, it is crucial to understand why Git is rejecting your push. Git is essentially a timeline tracking system. A “fast-forward” happens when the local branch is simply behind the remote branch, and Git only needs to move the branch pointer forward to catch up.
A non-fast-forward error occurs when the histories of your local branch and the remote branch have diverged.
The “Divergent Timeline” Analogy
Imagine you and a coworker, Sarah, are both working on the main branch.
1. At 9:00 AM, you both pull the latest code from GitHub. The latest commit is Commit A.
2. At 10:00 AM, Sarah finishes a feature, commits her code (Commit B), and successfully pushes it to GitHub.
3. Meanwhile, at 10:30 AM, you finish your feature and create Commit C.
When you try to push to GitHub, Git looks at the remote repository. It sees that the remote history is now A -> B, but your local history is A -> C. Git refuses to overwrite Sarah’s work (Commit B) with your push. Because your local branch is missing Commit B, Git cannot safely “fast-forward” the pointer. It throws the non-fast-forward rejection to protect existing data.
Step-by-Step Solutions: How to Fix GitHub Push Rejected Non Fast Forward
Now that you understand the “why,” let’s look at the “how.” We will progress from the safest, most common solutions to more aggressive overrides.
Solution 1: The Standard Git Pull (Merge Strategy)
The safest and most common way to resolve this issue is to integrate the remote changes into your local branch before pushing.
Step 1: Fetch and merge the remote changes.
git pull origin main
(Note: Replace main with your actual branch name, e.g., master or develop).
Step 2: If Git detects overlapping changes, it will pause the merge and ask you to resolve a merge conflict. Open your IDE (like VS Code or IntelliJ), look for the conflicted files, and choose how to combine the code.
Step 3: Once resolved, stage the files and commit the merge.
git add .
git commit -m "Merge remote-tracking branch 'origin/main'"
Step 4: Push your code.
git push origin main
When to use this: This is the default behavior for most developers. It preserves the exact history of both branches but introduces a “merge commit” into your git log, which some teams prefer to avoid.
Solution 2: The Git Pull Rebase (Clean History Strategy)
If you want to keep your project’s history linear without those extra “Merge branch…” commits, the rebase strategy is your best friend. This is highly recommended for modern CI/CD workflows where clean, readable logs are prioritized.
Instead of creating a merge commit, rebasing temporarily sets aside your new commits, downloads the remote commits, and then places your commits right on top of the newest remote code.
Step 1: Pull using the rebase flag.
git pull --rebase origin main
Step 2: If there are conflicts, Git will pause the rebase. Resolve them in your IDE.
Step 3: Unlike a merge, you do not create a new commit after resolving. Instead, you stage the files and tell Git to continue applying your commits.
git add .
git rebase --continue
(You might need to repeat steps 2 and 3 if you have multiple local commits that conflict).
Step 4: Push your code. Because your local commits are now stacked neatly on top of the remote history, it will be a fast-forward push.
git push origin main
When to use this: When your team prefers a linear git history and you want to avoid unnecessary merge commits cluttering the timeline.
Solution 3: Force Pushing (Overwriting Remote History)
Sometimes, the remote branch has commits that you want to overwrite. This frequently happens when you have just used an interactive rebase (git rebase -i) to squash your commits together, or you amended your last commit (git commit --amend).
Because you have altered the cryptographic hashes of your local history, Git will see this as a non-fast-forward error. In these specific scenarios, you must tell Git to forcefully overwrite the remote branch.
The Safe Way (Highly Recommended):
git push --force-with-lease origin main
--force-with-lease is the safest way to force push. It checks to ensure that the remote branch hasn’t been updated by someone else (like our coworker Sarah) since you last fetched. If it has been updated, the push is safely rejected, preventing you from accidentally deleting a teammate’s hard work.
The Nuclear Option (Use with Extreme Caution):
git push -f origin main
The -f or --force flag unconditionally overwrites the remote branch.
⚠️ Developer Warning: Never use
git push -fon shared branches likemain,master, ordevelopwithout strict team coordination. Doing so can permanently delete other developers’ commits from the remote server. Only force push to your own feature branches (e.g.,feature/new-login).
Edge Cases and Advanced Troubleshooting
If the standard solutions above didn’t work, you might be dealing with a more obscure Git scenario. Here are a few advanced troubleshooting steps.
Edge Case 1: Git Pull Fails with “Local Changes Overwrite”
Sometimes, when you try to run git pull, Git refuses to even fetch the code because you have uncommitted local changes that would be overwritten by the incoming remote commits.
You will see an error like:
error: Your local changes to the following files would be overwritten by merge:
src/config.js
Please commit your changes or stash them before you merge.
The Fix: Use Git Stash
The git stash command takes your uncommitted work and saves it in a temporary stack, leaving your working directory clean.
# 1. Save your local changes
git stash
# 2. Pull the remote changes
git pull origin main
# 3. Re-apply your local changes on top
git stash pop
Once you pop the stash, you might still need to resolve minor conflicts, but your local progress will not be lost.
Edge Case 2: Invisible Submodule Conflicts
If you are working in a repository that utilizes Git submodules, you might find that your push is rejected, but git status looks completely clean. This happens when a submodule is pointing to a different commit than the remote expects.
The Fix: Update your submodules before pushing.
git submodule update --init --recursive
git pull origin main
git push origin main
Edge Case 3: Recovering from a Botched Merge
A common mistake developers make when faced with the non-fast-forward error is to panic, resulting in a messy merge state full of conflicts they don’t know how to handle. If you have started a merge or rebase and want to start over, you can safely abort the process.
To cancel a stuck merge:
git merge --abort
To cancel a stuck rebase:
git rebase --abort
These commands instantly restore your repository to the exact state it was in before you ran the git pull command, giving you a clean slate to try a different strategy.
Edge Case 4: The CI/CD Bot Mystery Push
In modern environments, automated bots (like Dependabot, Renovate, or GitHub Actions themselves) might push directly to your branch or a protected branch. If you are pushing from a local terminal and getting a non-fast-forward error, but you are the only human working on the project, an automated process likely modified the remote.
The Fix: Always check the Git reflog and remote activity.
git fetch origin
git log origin/main
Review the log. If you see automated commits (e.g., “chore(deps): update package”), a simple git pull --rebase is usually all you need to layer your code on top of the bot’s changes.
CI/CD Implications: Fixing Non-Fast-Forward in Pipelines
The non-fast-forward error doesn’t just happen on local machines. It frequently breaks GitHub Actions, GitLab CI, or Jenkins pipelines. If your deployment script pushes code, tags, or documentation back to the repository, it can easily be rejected if another process pushed code milliseconds before.
Scenario 1: Pipeline Generating Artifacts
Imagine a GitHub Actions workflow that automatically generates API documentation and pushes it to the gh-pages branch. If two developers merge pull requests simultaneously, two pipeline runs will trigger.
Pipeline A finishes first and pushes the docs. Pipeline B finishes second, attempts to push, and fails because the remote gh-pages branch has moved forward.
The Fix for CI/CD Pipelines:
In automated environments where two concurrent jobs won’t edit the exact same lines of generated content, --force is often the most practical solution to avoid complex merging logic in bash scripts.
“`yaml
Example GitHub Actions step
- name: Deploy Documentation
run: |
git config –global user.name “github-actions[bot]”
git config –global user.email “github-actions[bot]@users.noreply.github.com”
git add docs/
git commit -m “Auto-generate documentation