TechEarl

Git: 'pre-receive hook declined' - How to Fix It

The 'pre-receive hook declined' error means the server rejected your push. Here is how to tell which rule blocked you (branch protection, file size, secret scan) and fix it.

Ishan Karunaratne⏱️ 8 min readUpdated
Share thisCopied
How to diagnose and fix the Git pre-receive hook declined error when a push is rejected by the server

The pre-receive hook declined error means the server accepted your connection but a server-side hook rejected the push before writing anything. Git did its part; the host (GitHub, GitLab, Bitbucket, or your own server) said no. The one-line fix depends on why it said no, and the real reason is almost always printed in the lines just above this message. Scroll up and read them first.

Here is the error you are looking at:

text
 ! [remote rejected] main -> main (pre-receive hook declined)
error: failed to push some refs to 'github.com:you/your-repo.git'

That [remote rejected] line is the key. The push left your machine fine. It reached the remote, the remote ran a pre-receive hook, and the hook exited non-zero, which aborts the whole push. Nothing changed on the server. This is different from a plain rejected push you can fix by pulling first, where the server is fine and your local branch is just behind.

What a pre-receive hook actually is

A pre-receive hook is a script that runs on the server the instant a push arrives, before any ref is updated. It sees every ref you are trying to change and can reject all of them at once. Hosts use it to enforce policy: branch protection, required reviews, file-size caps, secret scanning, commit-message rules. If the script exits with a non-zero status, the push is declined and you get the message above.

You cannot edit or bypass this hook from your machine. It lives on the server and runs with the server's permissions. That is the whole point: it is the one place a host can enforce a rule that a developer cannot skip. So fixing the error never means "turning off the hook" from your side. It means making your push satisfy the rule, or getting someone with admin access to change the rule. If you want the mechanics of where hooks live and how they run, I wrote a longer walkthrough in Git hooks explained, with a pre-commit example; the pre-receive hook is the server-side cousin of the local pre-commit hook.

Read the lines above the error

Almost every host prints the real reason as remote: lines just before the [remote rejected] line. Those are the hook's own output, forwarded to you. A protected-branch rejection looks like this:

text
remote: error: GH006: Protected branch update failed for refs/heads/main.
remote: error: Required status check "build" is expected.
 ! [remote rejected] main -> main (pre-receive hook declined)
error: failed to push some refs to 'github.com:you/your-repo.git'

A file-size rejection looks like this:

text
remote: error: GH001: Large files detected. You may want to try Git Large File Storage.
remote: error: File assets/video.mp4 is 142.00 MB; this exceeds GitHub's file size limit of 100.00 MB.
 ! [remote rejected] main -> main (pre-receive hook declined)

A secret-scanning rejection looks like this:

text
remote: error: GH013: Repository rule violations found for refs/heads/main.
remote:   - Push cannot contain secrets
remote:   Locations:
remote:     commit: 9f3a1c2
remote:       path: config/.env:4
 ! [remote rejected] main -> main (pre-receive hook declined)

If you pushed and the terminal scrolled past these, push again and read carefully. The remote: prefix marks the lines that came from the server's hook, and that is where the answer is.

The four common causes and how to fix each

1. You pushed to a protected branch

This is the most common cause on GitHub. The branch (usually main or master) has a protection rule that blocks direct pushes, requires a pull request, requires reviews, or requires status checks to pass. The hook rejects your direct push because policy says changes to that branch must go through review.

The fix is to stop pushing straight to the protected branch and go through a branch and a pull request instead:

bash
git switch -c fix/login-redirect
git push -u origin fix/login-redirect

Then open a pull request from that branch. If you are new to that flow, how to create a pull request walks through it end to end, and Git branching explained covers why feature branches exist in the first place. For the bigger picture of why pushing through a feature branch is the team default, the branch-and-PR loop is the whole point. If you genuinely need direct pushes to main (a solo repo, say), a repo admin can relax the rule. I cover what those settings do in how to protect your main branch on GitHub.

2. A file is over the size limit

GitHub rejects any file over 100 MB outright, and warns above 50 MB. The hook declines the whole push because one blob is too big. Crucially, deleting the file in a new commit does not help: the large blob still lives in the history you are trying to push.

You have to remove the file from history, or move it to Git Large File Storage. I cover the cleanup in detail in the GitHub file size limit error. The short version when the offending file is in your most recent commit:

bash
git rm --cached assets/video.mp4
echo "assets/video.mp4" >> .gitignore
git commit --amend --no-edit
git push

For a file buried several commits back, you need to rewrite history with git filter-repo (see the file-size article). And add the pattern to your .gitignore so it never gets staged again.

3. Secret scanning caught a credential

Push protection refuses commits that contain recognized secrets: API keys, tokens, private keys, cloud credentials. The fix is to remove the secret from history (a fresh commit deleting the file is not enough, same as the large-file case), rotate the leaked credential, and only then push.

bash
# remove the file holding the secret from the last commit
git rm --cached config/.env
git commit --amend --no-edit
git push

If the secret is older than your last commit, you have to scrub it from the full history. I wrote a step-by-step for exactly that in how to remove a secret from Git history. Treat any committed secret as compromised the moment it leaves your machine and rotate it regardless of whether the push succeeded, because a leaked .git directory can hand the whole history to an attacker, as I covered in the exposed .git directory attack.

4. A custom server policy rejected the commit

On self-hosted GitLab, Bitbucket Server, or a bare repo with your own pre-receive script, the hook can enforce anything the host set up: a commit-message convention, a signed-commit requirement, a "no force-push" rule, a JIRA-ticket reference. The remote: lines tell you which rule failed. Read them, fix the commit to comply (often a reword, or squash the messy commits with an interactive rebase), and push again. If the rule itself is wrong, the server admin is the one who edits the hook.

How to tell which cause you hit

What the remote: lines sayCauseFix
GH006, "Protected branch", "required status check", "review required"Branch protectionPush a feature branch, open a pull request
GH001, "Large files detected", "exceeds ... file size limit"File over size limitRemove from history or move to Git LFS
GH013, "cannot contain secrets", "Push protection"Secret scanningScrub the secret from history, rotate the credential
A custom message (commit format, signing, ticket ID)Custom server hookMake the commit comply, or ask the admin

The pattern is the same every time: the (pre-receive hook declined) line tells you a hook said no, and the remote: lines above it tell you which hook and why.

What does not fix it

A few things people reach for that will not help, because the problem is on the server, not your local branch:

  • git push --force does not bypass a pre-receive hook. The hook runs after the push arrives, force or not, and a protected branch usually blocks force-pushes specifically.
  • git pull then push does not help unless the lines above genuinely said your branch was behind (that is a different error). For a true rejection, you have to satisfy the policy.
  • Deleting the offending file in a new commit does not remove it from the history you are pushing. The blob is still in an earlier commit. You have to rewrite history, which is why the reflog is worth knowing before you rewrite anything.

If you are still mapping out the basics of pushing, branching, and remotes, start at my Git for beginners guide, which links every step in the workflow this error sits inside.

Sources

Authoritative references this article was fact-checked against.

Tagspre-receive hook declinedGitVersion Controlbranch protectionGitHub

Found this useful? Pass it on.

Copied

Ishan Karunaratne

Tech Architect · Software Engineer · AI/DevOps

Tech architect and software engineer with 20+ years building software, Linux systems, and DevOps infrastructure, and lately working AI into the stack. Currently Chief Technology Officer at a healthcare tech startup, which is where most of these field notes come from.

Keep reading

Related posts