What this error means
Git resolves HEAD to the current commit. The error fatal: bad object HEAD means Git cannot resolve HEAD to a valid commit object. Common causes:
- Unborn branch: repository has no commits yet.
- Broken symbolic ref: .git/HEAD points to a branch that does not exist.
- Missing branch ref: refs/heads/<branch> points to a non-existent commit.
- Corruption or incomplete clone: required objects are missing locally.
- After history rewrite: local branch ref points to a commit that was garbage-collected or never fetched.
Quickstart fixes
Use these in the suspected repository directory.
- Check current state:
- git status
- git rev-parse --abbrev-ref HEAD
- cat .git/HEAD
- If this is a brand-new repo (no commits):
- git commit --allow-empty -m "Initial commit"
- If .git/HEAD refers to a missing branch (e.g., refs/heads/main):
- git fetch origin
- git symbolic-ref HEAD refs/heads/main
- git reset --hard origin/main
- If you know a good commit hash to restore a branch:
- git update-ref refs/heads/main <commit>
- git symbolic-ref HEAD refs/heads/main
- If you suspect corruption or missing objects:
- git fetch --all --prune --tags
- git fsck --full
- If unresolved, reclone or restore from a known-good remote/backup.
Minimal working example (reproduce and fix)
The script below simulates a broken branch ref, triggers the error, then fixes it.
# Create a temporary repo
set -euo pipefail
WORKDIR=$(mktemp -d)
cd "$WORKDIR"
# Initialize a repo with a 'main' branch
if git init -b main 2>/dev/null; then :; else git init && git checkout -b main; fi
echo hello > file.txt
git add file.txt
git commit -m "init"
# Record the current good commit
GOOD=$(git rev-parse HEAD)
# Break the branch ref (simulate missing refs/heads/main)
rm -f .git/refs/heads/main
# Commands referencing HEAD now fail
set +e
git rev-parse HEAD
# -> fatal: bad object HEAD
set -e
# Fix: restore the branch ref to the last known good commit
git update-ref refs/heads/main "$GOOD"
# Ensure HEAD points to that branch
git symbolic-ref HEAD refs/heads/main
# Verify
git rev-parse HEAD
git log --oneline -1
Result: the error disappears and HEAD is resolvable again.
Step-by-step diagnosis and repairs
- Inspect HEAD and branches
- cat .git/HEAD
- Expected: ref: refs/heads/<branch>
- If it is a raw hash or empty, HEAD is corrupt; reset it with git symbolic-ref HEAD refs/heads/<branch>.
- List branch refs: git show-ref --heads
- List branches: git branch -a
- Unborn branch (no commits yet)
Symptoms:
- git status shows “No commits yet on <branch>”.
- git log fails and HEAD is unresolved in some commands.
Fix:
- Create the first commit:
- git commit --allow-empty -m "Initial commit"
- Or add a file, then git add . && git commit -m "Initial commit"
- .git/HEAD points to a non-existent branch
Symptoms:
- cat .git/HEAD shows ref: refs/heads/main but git branch does not list main.
Fix options:
- If a remote branch exists:
- git fetch origin
- git symbolic-ref HEAD refs/heads/main
- git branch -f main origin/main
- git reset --hard origin/main
- If you know the target commit (via CI logs, notes, or reflogs):
- git update-ref refs/heads/main <commit>
- git symbolic-ref HEAD refs/heads/main
- Branch ref exists but points to a missing commit
Symptoms:
- git show-ref --heads shows refs/heads/main <hash>, but git show <hash> fails.
Fix:
- Fetch missing objects: git fetch --all --prune --tags
- If this branch tracks a remote: git reset --hard origin/main
- If history was rewritten on the remote, align with the new tip:
- git fetch origin
- git branch -f main origin/main
- git reset --hard origin/main
- Recover from reflogs (local recovery)
- Show branch reflog (may work even if HEAD is broken):
- git reflog show refs/heads/main
- Pick a known-good commit from the reflog, then:
- git update-ref refs/heads/main <good-commit>
- git symbolic-ref HEAD refs/heads/main
- git reset --hard <good-commit>
- Detect and address corruption
- Verify object database: git fsck --full
- Missing blobs/commits: fetch from remote or restore from backup.
- Dangling objects are usually benign; missing required objects are not.
- Try cleanup: git gc --prune=now --aggressive (can be slow; see Performance notes).
- If corruption persists and you have a remote, reclone:
- git clone --mirror <url>
- Validate with git fsck, then replace the broken repo if needed.
- Special environments
- Worktrees: run git worktree list to find the main worktree. Refs live in the primary repository’s .git directory; fix refs there if needed.
- Submodules: cd into the submodule directory and apply the same diagnostics. Each submodule has its own .git data (sometimes as a gitdir link).
- Verify and prevent recurrence
- Verify:
- git rev-parse HEAD
- git log -1 --oneline
- git status
- Prevention:
- Avoid manual edits to .git files; use git symbolic-ref and git update-ref.
- Keep remotes up to date: git fetch --all regularly.
- Avoid long-lived detached HEADs for active development.
Pitfalls
- Resetting the wrong branch: Always confirm the branch name you set in HEAD.
- Hard resets discard uncommitted changes: stash or commit first.
- Forcing HEAD to a non-tracking branch can break tooling that expects origin/<branch>.
- Manually editing .git/HEAD or packed-refs risks typos and further breakage; prefer plumbing commands.
- In CI, shallow clones may lack required objects; use fetch-depth: 0 or an explicit fetch after checkout.
Performance notes
- git fsck --full and git gc --aggressive are CPU and I/O heavy on large repos; run them sparingly and outside hot paths (e.g., not during every CI job).
- When a remote has the full history, fetching the missing refs and resetting is usually faster than running aggressive garbage collection.
- Recloning can be the fastest fix for large, corrupted working copies when network bandwidth is good.
- Use sparse-checkout and partial clones to reduce object transfer in CI; these still maintain a valid HEAD.
Tiny FAQ
Why did this start after a rebase or filter?
Your local branch ref may point to a commit that was rewritten and garbage-collected. Fetch and reset to the updated remote tip.Can I just edit .git/HEAD?
Yes, but it’s safer to run: git symbolic-ref HEAD refs/heads/<branch>.How do I know my default branch name?
Try: git symbolic-ref --short HEAD (when healthy), or check remote: git ls-remote --symref origin HEAD.Does recloning lose local work?
Unpushed commits will be lost unless you export them first (git bundle create, or git format-patch) and import into the new clone.