A few weeks ago I wrote about a git bundle problem: git bundle verify reported "complete history," git clone exited 0, and the working tree was empty. The root cause was a missing HEAD ref in the bundle. The fix was to create the bundle with --all HEAD.
A commenter named SystemCraftDev added the recovery side: what to do if you already have a bundle without HEAD and cannot re-create it. Their commands work, and they deserve to be in the same place as the original post. Here is the complete guide.
The problem, in one paragraph
git bundle create transfer.bundle main creates a bundle with one ref (refs/heads/main) but no HEAD. When you clone from it, git tries to resolve HEAD, finds it pointing at a branch that does not exist (usually master), and checks out nothing. The clone succeeds with exit code 0. The working tree is empty. The objects are all there. There is simply no branch and no checkout.
Prevention: create the bundle with HEAD
git bundle create transfer.bundle --all HEAD
--all takes every ref. HEAD adds the one that makes it clonable. That bundle lists multiple heads including HEAD, and the clone checks out a working tree with a branch on it.
For a single branch:
git bundle create transfer.bundle main HEAD
Recovery: you already have a bundle without HEAD
If you cannot re-create the bundle, you can work around the missing HEAD by naming the branch explicitly during clone:
git clone -b main transfer.bundle restored
The -b main flag tells git which branch to check out, bypassing the HEAD resolution entirely. Git creates the local branch from the bundle's refs/heads/main and checks out the files.
This works because the objects and the ref are in the bundle. The only thing missing is HEAD, and -b lets you specify the branch directly instead of relying on HEAD to point to it.
Recovery: you already cloned and got an empty tree
If you already ran git clone transfer.bundle restored and ended up with an empty working tree, the objects are still there. You can recover in place:
git switch main
Git creates the local branch from the remote-tracking ref (origin/main) and checks out the files. No need to re-clone.
If you are not sure what branches are available in the broken clone:
git branch -r
This lists the remote-tracking refs. You will see origin/main (or whatever branch was in the bundle). Switch to it with git switch main or git switch -c main origin/main.
How to read the empty-clone symptom
The symptom that confused me was:
$ git -C restored symbolic-ref HEAD
refs/heads/master
HEAD points at refs/heads/master, which does not exist, because the bundle carried main. Git has nothing to check out. The warning on stderr (remote HEAD refers to nonexistent ref) is the only signal that something is wrong. Under set -e, the exit code 0 means nothing stops.
The HEAD ref in a bundle tells git which branch to check out after clone. Without it, git falls back to its default (usually master), and if the bundle does not contain master, you get an empty tree with no branch.
The complete decision tree
| Situation | Command |
|---|---|
| Creating a bundle | git bundle create transfer.bundle --all HEAD |
| Cloning from a bundle with HEAD | git clone transfer.bundle restored |
| Cloning from a bundle without HEAD | git clone -b main transfer.bundle restored |
| Already cloned, empty tree, want to recover | git switch main |
| Not sure which branch is in the clone | git branch -r |
Why this matters in automated workflows
The original problem surfaced in a transfer script. The script ran git bundle verify (exit 0, "complete history"), then git clone (exit 0), then checked for files (none). Under set -e, nothing stopped. The only evidence was a warning on stderr, which the script logged to stdout and never surfaced.
If you are writing a transfer script, check for the working tree, not the exit code:
git clone transfer.bundle restored
if [ -z "$(ls -A restored)" ]; then
echo "Clone succeeded but working tree is empty"
echo "Attempting recovery with explicit branch..."
git -C restored switch main 2>/dev/null || git -C restored switch -c main origin/main
fi
This catches the empty-tree case and attempts recovery in place. It is not elegant, but it handles the one failure mode that exit codes cannot detect.
Thanks to the commenter
SystemCraftDev's comment on the original post added the recovery commands that I had not covered. The original post was about prevention (how to create the bundle correctly). The recovery side is equally important, especially for bundles you receive from someone else and cannot re-create. This article puts both sides in one place.
Top comments (0)