Git Error: 'hook declined to update refs/heads/main' — Cause, Fix, and Troubleshooting Guide
Fix Git's 'remote: error: hook declined to update refs/heads/main' and 'pre-receive hook declined' — branch protection, push rules, file size, and policy hooks.
- #automation
- #troubleshooting
- #git
Fixing errors like this? Get 500 free DevOps AI prompts
500 copy-paste AI prompts for the stack you actually run — one PDF, free.
Overview
A pre-receive hook runs on the Git server the moment you push, before any ref is updated. If that hook exits non-zero, the server rejects the entire push and none of your commits land. Unlike a client-side hook you can inspect locally, this one lives on the remote — so the only clue you get is whatever the hook chooses to print, prefixed with remote::
$ git push origin main
Enumerating objects: 9, done.
Counting objects: 100% (9/9), done.
Writing objects: 100% (5/5), 612 bytes | 612.00 KiB/s, done.
Total 5 (delta 3), reused 0 (delta 0)
remote: error: GL-HOOK-ERR: commit a1b2c3d has no linked issue in the message
remote: error: hook declined to update refs/heads/main
To example.com:team/service.git
! [remote rejected] main -> main (pre-receive hook declined)
error: failed to push some refs to 'example.com:team/service.git'
The three lines that matter, in order: the remote: error: line(s) with the actual reason, hook declined to update refs/heads/main, and ! [remote rejected] main -> main (pre-receive hook declined). The push is atomic — a single declined ref rejects everything in that push.
Symptoms
remote: error: hook declined to update refs/heads/<branch>on push.! [remote rejected] <branch> -> <branch> (pre-receive hook declined)in the push summary.- The failure is server-side: it happens after “Writing objects” completes, not during your local commit.
- Pushing to a different branch (a feature branch instead of
main) succeeds, whilemain/master/release/*is rejected. - The reason text varies by platform:
GL-HOOK-ERR(GitLab),remote: error: File X is ... MB; this exceeds ...(size limits), commit-message policy messages, or a bespoke company hook.
Common Root Causes
1. Protected branch / branch protection rule
The target branch (main, master, release/*) is protected and forbids direct pushes, force-pushes, or pushes that bypass review. The platform enforces this with a pre-receive hook, so the low-level error you see is “hook declined.”
2. Server-side lint, test, or policy check failed
The hook runs a linter, secret scanner, or CI gate synchronously and rejects the push when it fails. Typical messages call out a specific file, rule, or offending commit.
3. GitLab / Gerrit push rules
Push rules can require a commit-message regex, a signed commit, a specific author email domain, or reject certain filenames. Violations surface as GL-HOOK-ERR: ... followed by the declined line.
4. File-size or LFS policy limit
A blob exceeds the server’s maximum object size (common on hosted platforms), or a large file was committed without Git LFS. The hook rejects the push and names the file and its size.
5. Commit-message or signature policy
A hook that requires a linked issue key (PROJ-123), a Signed-off-by trailer, or a GPG signature declines commits that lack it.
6. A bug in the server hook itself
Occasionally the hook script errors out (missing interpreter, bad path, a broken dependency on the server) and exits non-zero for reasons unrelated to your commits. The message is often less specific — a stack trace or command not found echoed through remote:.
How to Diagnose
Read the remote: lines first — they carry the real reason. Everything not prefixed with remote: is generic Git plumbing:
git push origin main 2>&1 | grep '^remote:'
remote: error: GL-HOOK-ERR: commit message must reference an issue (e.g. PROJ-123)
remote: error: hook declined to update refs/heads/main
Confirm whether the branch is protected. On GitHub/GitLab this is in the repo settings; from the CLI you can check with the platform tool:
# GitHub
gh api repos/team/service/branches/main/protection --jq '.required_status_checks, .required_pull_request_reviews' 2>/dev/null
# GitLab
glab api projects/:id/protected_branches 2>/dev/null
{"strict":true,"contexts":["ci/lint"]}
{"required_approving_review_count":1}
Inspect what you are actually trying to push, so you can see which commit or file the hook objects to:
git log --oneline origin/main..HEAD
git diff --stat origin/main..HEAD
a1b2c3d add bulk import
9f8e7d6 wip
data/dump.sql | 1 +
src/import.py | 42 +++++++
If the message mentions file size, find the large blob before you try again:
git rev-list --objects --all \
| git cat-file --batch-check='%(objecttype) %(objectsize) %(rest)' \
| awk '/^blob/ && $2 > 5000000 {print $2, $3}' | sort -n | tail
104857600 data/dump.sql
If you have server access (self-hosted), the hook’s own logs are authoritative:
# On the Git server
sudo tail -50 /var/log/gitlab/gitlab-shell/gitlab-shell.log
sudo tail -50 /opt/git/repositories/service.git/hooks/pre-receive.log
Fixes
Satisfy the policy rather than fighting it. If the hook demands an issue key in the message, amend the offending commit and push again:
git commit --amend -m "add bulk import (PROJ-123)"
git push origin main
remote: Resolving deltas: 100% (3/3), completed with 4 local objects.
To example.com:team/service.git
9f8e7d6..a1b2c3d main -> main
For a protected branch, push through the sanctioned path — a feature branch plus a pull/merge request — instead of directly to main:
git push origin HEAD:feature/bulk-import
gh pr create --base main --head feature/bulk-import --fill
For a rejected large file, remove it from history and route it through Git LFS or object storage:
git rm --cached data/dump.sql
echo 'data/dump.sql' >> .gitignore
git commit --amend --no-edit
# or track it properly with LFS
git lfs track '*.sql'
git add .gitattributes data/dump.sql && git commit --amend --no-edit
Reproduce the check locally before you push again, so you are not iterating against the server. Many teams ship the same linter as a client-side hook (see the pre-commit guide below) so violations are caught at commit time.
If the message indicates a hook bug rather than a policy you violated (a stack trace, command not found, or a check unrelated to your change), you cannot fix it from the client — capture the full remote: output and contact the repository or platform admin:
git push origin main 2>&1 | tee push-error.log
What to Watch Out For
- The push is atomic per invocation. One declined ref rejects the whole push, so a single bad commit blocks everything you were pushing.
--forcedoes not bypass apre-receivehook; the hook runs regardless and protected branches typically reject force-pushes outright.- The
remote:prefix is added by Git to anything the server hook writes to stderr. Do not ignore those lines as noise — they are the only diagnostic you get. - Amending or rebasing to satisfy a message/signature policy rewrites commit SHAs. Coordinate if others have already based work on those commits.
- Server hook logs live on the platform, not in your clone. On hosted services you will need the admin or the platform’s audit UI; only self-hosted setups give you
tailaccess.
Related Guides
- Git Hook Error: pre-commit hook not found or not executable
- AI-Assisted Pre-Commit Hooks for Automation Repos
- GitHub Actions: Process completed with exit code 1
Fixed it? Get 500 Automation & DevOps AI prompts — free
500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.
- 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
- Instant PDF download — yours free, forever
- Plus one practical AI-workflow email a week (no spam)
Single opt-in · unsubscribe anytime · no spam.
Did this fix your issue?
Get 500 Battle-Tested DevOps AI Prompts — Free
500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.
- 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
- Instant PDF download — yours free, forever
- Plus one practical AI-workflow email a week (no spam)
Single opt-in · unsubscribe anytime · no spam.