Git Detached HEAD — What It Is and How to Fix It

I still remember the first time this bit me. I'd checked out an old commit to confirm a bug existed before a certain release, fixed it right there because I was already looking at the code, committed, and pushed nothing because I got pulled into a meeting. Came back an hour later, ran git switch main, and my two commits were gone. Not on any branch. Not in the log. Gone.

They weren't actually gone, but I didn't know that yet. Here's what was going on.

What HEAD normally points at #

HEAD is Git's pointer for "where you are right now." In normal life it points at a branch, like main, and the branch points at a commit. When you make a new commit, the branch moves forward and drags HEAD along with it. That indirection is the whole trick: your branch name follows your work automatically.

Detached HEAD is when that middle step disappears. HEAD points straight at a commit instead of at a branch. You're standing on a specific snapshot with no branch label attached to your position.

You land here a few ways:

git checkout <sha> or git switch --detach <sha> — jumping to a specific commit

git checkout v1.4.0 — checking out a tag (tags aren't branches)

git bisect — the whole mechanism walks you across commits detached

Submodules — they're pinned to an exact commit, so a fresh submodule checkout is detached by design

When it happens, Git tells you plainly:

code

Note: switching to 'a1b2c3d'.

You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by switching back to a branch.

That text is worth reading once, slowly. It's not an error. It's Git warning you about the tradeoff you just accepted.

Why it's a warning, not a failure #

Nothing is broken in detached HEAD. You can read files, run the build, check out a suspicious old commit and reproduce a bug. All of that is fine, and half the time it's exactly why you went there.

The catch is committing. If you make commits in detached HEAD, they exist, but no branch points at them. The only thing pointing at your new work is HEAD itself. The moment you switch to a branch, HEAD moves, and those commits have nothing holding them in place. Git's garbage collection eventually sweeps up unreferenced commits, so "eventually" they really do disappear.

That's the trap I fell into. My two commits were real, they just had no name. Switching branches let go of the only handle I had on them.

Fix 1: you made no commits, just get back #

If you only looked around and didn't commit anything, there's nothing to save. Return to your branch:

code

git switch main

Or the older spelling, still fine:

code

git checkout main

Done. No drama. This is the common case and it's why the warning exists but rarely causes real pain.

Fix 2: you made commits and want to keep them #

This is the case that matters. You're in detached HEAD, you committed real work, and you want it to survive. Do NOT switch branches yet. First, give your work a name:

code

git switch -c fix/old-release-bug

That creates a new branch right where you're standing, and every commit you made in detached state is now on it, safe. HEAD is reattached to a real branch. From here you push, open a PR, merge, whatever you normally do.

The opinionated version of this: create the branch before you commit, not after. The instant you decide you're going to write code in a detached checkout, run git switch -c something first. It costs you nothing, and it turns the whole "lost commits" problem into a non-event. I've never once regretted making a throwaway branch. I've regretted the other choice plenty.

Fix 3: you already switched away and lost the commits #

This is where people panic, and it's the most recoverable situation of all. Your commits aren't in any branch's history, but Git wrote down every place HEAD has ever been. That log is the reflog.

code

git reflog

You'll get something like:

code

a1b2c3d HEAD@{0}: checkout: moving from a1b2c3d to main
9f8e7d6 HEAD@{1}: commit: fix null check in parser
3c2b1a0 HEAD@{2}: commit: reproduce the crash
a1b2c3d HEAD@{3}: checkout: moving from main to a1b2c3d

There they are. 9f8e7d6 is the tip of the work I thought I'd lost. Grab it with a branch:

code

git branch recovered 9f8e7d6

Now switch to it, confirm the log looks right, and carry on. The reflog keeps entries for 90 days by default, so unless you waited a season or ran an aggressive git gc, your commits are sitting there waiting.

When detached HEAD is the right tool #

None of this means detached HEAD is a mistake to avoid. It's genuinely useful. Inspecting an old state to answer "did this bug exist at the v2.0 tag" is cleaner detached than creating a branch you'll throw away. git bisect depends on it. And CI systems check out a specific commit SHA on purpose — a build should be reproducible against an exact snapshot, not against whatever main happens to be right now. If you see detached HEAD in a CI log, that's correct behavior, not a bug in your pipeline.

The rule is simple. Detached HEAD for looking: perfect. Detached HEAD for committing: only after you've made a branch to catch the work.

The call we'd make #

Treat the warning as a checklist, not noise. If you're only inspecting, ignore it and git switch back when you're done. If there's any chance you'll commit, run git switch -c name the second you land, before you touch a file. And if you're reading this because your commits already vanished, breathe, run git reflog, and branch off the SHA — the work is almost certainly still there. For the wider set of these, our Git troubleshooting guide collects the ones that catch people most.