What this error means
Git prints "fatal: reference is not a tree" when a ref (branch, tag, submodule pointer, or raw SHA) points to a commit object your repository does not have locally. Common causes:
- Shallow or partial clones missing history
- Submodules pointing at a commit not fetched or not present on the submodule remote
- Tags or branches referencing unreachable commits after history rewrites
- Repository corruption or missing packfiles
Quickstart (most common fixes)
Try these in order, depending on your setup:
- Shallow clone: git fetch --unshallow --tags
- Partial clone: git fetch --refetch --filter=blob:none --tags; if still failing, git fetch --unshallow
- Submodules: git submodule sync --recursive && git submodule update --init --recursive --depth=2147483647
- History rewrite on remote: git fetch --all --prune && git reset --hard origin/main (replace main as needed)
- Last resort: verify the object exists on remote; reclone if necessary
Minimal working example (reproduce and fix)
This example shows the error from a shallow clone when checking out an old tag.
# Create a bare remote
rm -rf /tmp/remote.git /tmp/src /tmp/shallow
git init --bare /tmp/remote.git
# Create a repo with two commits and a tag on the first commit
git init /tmp/src
cd /tmp/src
printf "one\n" > file.txt
git add file.txt
git commit -m "first"
FIRST_SHA=$(git rev-parse HEAD)
git tag v1 $FIRST_SHA
echo "two" >> file.txt
git commit -am "second"
git remote add origin /tmp/remote.git
git push origin HEAD:main
git push origin --tags
# Shallow clone only the latest commit
cd /tmp
git clone --depth 1 /tmp/remote.git shallow
cd shallow
# Fetch tags (they may point to unreachable commits in a shallow clone)
git fetch --tags
# Try to check out v1 (expected to fail in a shallow clone)
git checkout v1 # Likely: fatal: reference is not a tree: <sha>
# Fix: unshallow to get the missing commit, then check out
git fetch --unshallow --tags
git checkout v1
Diagnose step-by-step
- Capture the failing ref or SHA
- Note the SHA from the error if shown, or the ref you tried to use.
- Check if the object exists locally
- git cat-file -t <sha> # should print commit or tag; fails if missing
- git show -s --oneline <sha>
- Identify your clone type
- Shallow? git rev-parse --is-shallow-repository
- Partial clone? git config --get remote.origin.promisor && git config --get core.repositoryformatversion && git config --get remote.origin.partialclonefilter
- Check refs and reachability
- Tag pointing to missing commit? git show-ref --tags | grep <sha> -n || true
- Branch status: git show-ref --heads | grep <sha> -n || true
- Submodules
- See what’s pinned: git submodule status --recursive
- Ensure remotes are correct: git submodule sync --recursive
- Verify presence on remote
- git ls-remote origin <sha>
- If not found on remote, it was never pushed or was garbage-collected after a rewrite.
- Sanity check repository health
- git fsck --full
Fix by scenario
Shallow or partial clone
- Symptom: the ref exists, but its commit is missing locally; you’re in a shallow/partial repo.
- Fixes:
- Make it full: git fetch --unshallow --tags
- Or deepen incrementally: git fetch --depth=1000 --tags
- Partial clone: keep blobless but fetch commits: git fetch --refetch --filter=blob:none --tags; if still failing, unshallow.
Tags pointing to old commits
- Symptom: git checkout <tag> fails; tag exists but points to an older commit outside shallow history.
- Fixes:
- git fetch --unshallow --tags
- If tag was force-moved remotely, refresh: git fetch --tags --force --prune
Force-pushed or rewritten history
- Symptom: your branch or submodule ref points to a commit no longer on remote.
- Fixes:
- Refresh all: git fetch --all --prune
- Align to the remote (destructive): git reset --hard origin/<branch>
- If you need to preserve local work, rebase: git rebase --rebase-merges origin/<branch>
- If the remote lost commits you still need, coordinate with the repo owner or push the missing commit.
Submodules referencing missing commits
- Symptom: git submodule update --init --recursive prints fatal: reference is not a tree: <sha> for a submodule.
- Fixes:
- Synchronize URLs: git submodule sync --recursive
- Fetch required history: git submodule update --init --recursive --depth=2147483647
- Manually fetch the missing commit in the submodule:
- cd path/to/submodule && git fetch origin <sha> && git checkout <sha>
- If the commit truly isn’t on the submodule remote (after a rewrite), update the superproject to point to a valid commit and push that change.
Corruption or missing packfiles
- Symptom: git fsck reports issues; random objects missing.
- Fixes:
- Try to heal: git gc --prune=now && git repack -Ad && git fsck --full
- If still broken, reclone: rm -rf .git and git clone <url> . (or clone to a fresh directory)
Quick reference table
| Situation | Detection | Primary fix |
|---|---|---|
| Shallow clone | git rev-parse --is-shallow-repository | git fetch --unshallow --tags |
| Partial clone | git config --get remote.origin.promisor | git fetch --refetch --filter=blob:none; if needed unshallow |
| Submodule missing commit | git submodule status --recursive | submodule sync + update --recursive; fetch the commit |
| Tag unreachable | tag exists; checkout fails | fetch --unshallow --tags |
| Remote rewrite | ls-remote lacks SHA | fetch --all --prune; reset or rebase |
| Corruption | git fsck errors | gc/repack or reclone |
Pitfalls and safety
- Reset with care: git reset --hard discards local changes. Stash or branch first.
- Fetch --force/--prune updates refs to match the remote; you may lose references to deleted remote branches.
- Submodules use their own remotes. Fixing the superproject alone won’t fetch submodule commits.
- CI shallow clones often skip tags. If you need tags, fetch them explicitly.
Performance notes (CI/CD and large repos)
- Prefer unshallow only when necessary. For CI that needs tags and history, set fetch-depth: 0 (GitHub Actions) or use full clones in pipelines that build releases.
- Use partial clone with blob-less filter to keep history while minimizing bandwidth: git clone --filter=blob:none --recurse-submodules=no <url>.
- For monorepos, combine sparse-checkout with partial clones to avoid fetching irrelevant paths.
- Cache clones between CI jobs to avoid repeated deep fetches, but run periodic git gc to keep packfiles healthy.
FAQ
Why does this happen right after I clone?
- You likely performed a shallow or partial clone. Fetch more history or unshallow.
It happens only with submodules. What now?
- The superproject references a submodule commit your clone doesn’t have. Sync submodules and fetch that commit, or update the superproject to a valid pointer.
Can I fix without a destructive reset?
- Usually. Fetch the missing objects first. Only hard reset if you must align with a rewritten remote.
How do I verify the commit exists on the remote?
- git ls-remote origin <sha>. If it returns nothing, the commit isn’t on the remote; coordinate with the repo owner or push it.
Is my repository corrupted?
- Run git fsck --full. If it reports missing objects unrelated to your clone depth, consider recloning.