Daniel Moerner

fatal: bad object HEAD

Thanks to all my friends and colleagues for such a wonderful in-person week at the Recurse Center hub in Brooklyn. I got so much out of the pairing, the social environment, and especially the opportunity to present my work in-person at Presentations+. Part of me wishes I had spent a week in-person earlier, but part of me also believes it was so great precisely because it was so late in the batch and my work was in a good place for collaboration.

I worked late at the Hub on Friday night, and let my laptop battery die while I was watching a movie at the Anthology Film Archives. When I opened it today, I was met with:

user@work ~/s/etracker (main)> git status
error: object file .git/objects/92/4bc35594c3f5ede99dad7768d961eedef24af0 is empty
error: object file .git/objects/92/4bc35594c3f5ede99dad7768d961eedef24af0 is empty
error: object file .git/objects/92/4bc35594c3f5ede99dad7768d961eedef24af0 is empty
fatal: bad object HEAD

Uh-oh. That sounds terminal! But this is git, we can fix it right?

The first thing I verified is that my latest edits were both available locally, and had been pushed to Github. Fortunately, they were (Github). So I could just delete the local repository and fetch it again from Github. But let’s try to fix it in place.

Let’s see what’s going on:

user@work ~/s/etracker (main)> cat .git/HEAD
ref: refs/heads/main
user@work ~/s/etracker (main)> cat .git/refs/heads/main
924bc35594c3f5ede99dad7768d961eedef24af0
user@work ~/s/etracker (main)> ls -l .git/objects/92/4bc35594c3f5ede99dad7768d961eedef24af0
-r--r--r--. 1 user user 0 Feb  7 18:56 .git/objects/92/4bc35594c3f5ede99dad7768d961eedef24af0

To put it crudely (but to express the best of my understanding of the matter), the git HEAD references the current commit on the current branch. The current branch is “main”, and the current commit is empty. That is obviously bad.

One way to fix this is explained in this detailed Stack Overflow post. Since the working directory is up-to-date, you can just delete the bad object, fix all the references (which will then treat your working directory as modified relative to HEAD), and then re-commit. However, since the commit is already in my remote repository, I think I would then have to do a force-push to rewrite the remote history. I would like to avoid doing this.

So really we have a slightly different challenge: How can I get my local repository to match the remote repository exactly, without using any of the normal git commands in the current repository? Well, all we need to do is copy over a good copy of this object. Well, this is all a bit silly now, but here’s one way to do it:

user@work ~> mkdir test
user@work ~> cd test/
user@work ~/test> git clone https://github.com/dmoerner/etracker
Cloning into 'etracker'...
remote: Enumerating objects: 673, done.
remote: Counting objects: 100% (168/168), done.
remote: Compressing objects: 100% (87/87), done.
remote: Total 673 (delta 75), reused 134 (delta 53), pack-reused 505 (from 1)
Receiving objects: 100% (673/673), 263.91 KiB | 2.09 MiB/s, done.
Resolving deltas: 100% (375/375), done.
user@work ~/test> cd etracker/
user@work ~/t/etracker (main)> mv .git/objects/pack/pack-85b91a72599f73863365b8873a9a6d35df74d96b.pack .
fatal: bad object HEAD
user@work ~/t/etracker (main)> git unpack-objects < pack-85b91a72599f73863365b8873a9a6d35df74d96b.pack
Unpacking objects: 100% (673/673), 263.89 KiB | 1.69 MiB/s, done.
user@work ~/t/etracker (main)> sudo cp .git/objects/92/4bc35594c3f5ede99dad7768d961eedef24af0 ~/src/etracker/.git/objects/92/
user@work ~/t/etracker (main)> cd ~/src/etracker/
user@work ~/s/etracker (main)> git status
On branch main
Your branch is up to date with 'origin/main'.

nothing to commit, working tree clean

Fixed! So what’s happening here? The issue is that when we clone a remote repository from Github, the object files are packed. For more information, see the relevant chapter of the Git book. So you can’t just directly clone a repository and copy over the object.

However, there is a secret Git command, git unpack-objects, which will unpack a packfile. I say it’s “secret” because it is actually omitted from the Git shell autocompletion! But you can still type it out manually. Now this command has a weird quirk: You need to run it from the Git repo, but if the packfile is in the default location, it does nothing. So we mv the packfile out of the .git/objects/pack directory, thereby deliberately breaking our second repo. Then we can unpack it again, un-breaking our second repo, and copy the right object file back to the original repo, un-breaking the first repo! (You need to sudo cp because all these objects are kept read-only.)

A bit of a silly exercise, but HEAD re-attached, and we are back to normal.