KhueApps
Home/DevOps/Fix 'fatal: reference is not a tree' in Git: practical guide

Fix 'fatal: reference is not a tree' in Git: practical guide

Last updated: October 07, 2025

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

  1. Capture the failing ref or SHA
  • Note the SHA from the error if shown, or the ref you tried to use.
  1. Check if the object exists locally
  • git cat-file -t <sha> # should print commit or tag; fails if missing
  • git show -s --oneline <sha>
  1. 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
  1. 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
  1. Submodules
  • See what’s pinned: git submodule status --recursive
  • Ensure remotes are correct: git submodule sync --recursive
  1. 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.
  1. 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

SituationDetectionPrimary fix
Shallow clonegit rev-parse --is-shallow-repositorygit fetch --unshallow --tags
Partial clonegit config --get remote.origin.promisorgit fetch --refetch --filter=blob:none; if needed unshallow
Submodule missing commitgit submodule status --recursivesubmodule sync + update --recursive; fetch the commit
Tag unreachabletag exists; checkout failsfetch --unshallow --tags
Remote rewritels-remote lacks SHAfetch --all --prune; reset or rebase
Corruptiongit fsck errorsgc/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.

Series: Git

DevOps