Git

How to Fix "Git Detached HEAD State" and Save Local Changes

5 min read by DebuggedIt

Quick answer

Git warns you that you're in "detached HEAD" state after checking out a commit, tag, or remote branch directly instead of a local branch. Commits made here can...

Git warns you that you're in "detached HEAD" state after checking out a commit, tag, or remote branch directly instead of a local branch. Commits made here can be lost if you switch away without saving them to a real branch first.

The Problem

Checking out a specific commit hash, tag, or remote ref directly triggers the warning:

$ git checkout a1b2c3d
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.

If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -c with the switch command. Example:

  git switch -c <new-branch-name>

HEAD is now at a1b2c3d fix: patch auth bug

If you didn't notice the warning, made several commits, then switched to another branch, those commits appear to vanish:

$ git checkout main
Warning: you are leaving 1 commit behind, not connected to
any of your branches:

  f9e8d7c debug: added logging

$ git log --oneline
(the commits from detached HEAD are nowhere to be seen)
HEAD attached to a branch vs. detached Normal: HEAD -> branch -> commit HEAD main Detached: HEAD -> commit directly HEAD no branch points here

Why It Happens

A branch in Git is just a movable pointer to a commit, and HEAD normally points to a branch, not a commit directly. When you check out something that isn't a branch name β€” a raw commit SHA, a tag, or a remote-tracking ref like origin/main without -b β€” Git points HEAD straight at that commit instead. New commits you make still get created, but no branch pointer moves forward with them, so they become unreachable once you check out something else and Git's garbage collector eventually cleans them up. This typically happens when:

  • You checked out a specific commit to inspect old code: git checkout a1b2c3d.
  • You checked out a Git tag directly: git checkout v1.2.0.
  • You checked out a remote branch without -b or --track: git checkout origin/feature-x.
  • A CI pipeline or build tool checked out a specific commit as part of its normal process, and you kept working in that same checkout.

The Fix

If you're still in the detached HEAD state and haven't switched away yet, create a branch right now to save your position and any commits:

git switch -c recovered-work

or, with the older syntax:

git checkout -b recovered-work

This attaches HEAD to a brand-new branch pointing at your current commit, so nothing is lost β€” you can now merge it into main or push it normally:

git push -u origin recovered-work

If you already switched away and think you lost commits, don't panic β€” Git rarely deletes commit objects immediately. Use the reflog to find them:

git reflog
f9e8d7c HEAD@{1}: commit: debug: added logging
a1b2c3d HEAD@{2}: checkout: moving from main to a1b2c3d

Find the lost commit's hash in the list, then recover it into a new branch:

git branch recovered-work f9e8d7c

Still Not Working?

If git reflog doesn't show the commit (for example, it's been more than 90 days, the default expiry for unreachable commits, or you ran an aggressive garbage collection manually), the commit object may be gone for good. As a last resort, search dangling commits directly:

git fsck --full --no-reflogs --unreachable --lost-found

This lists dangling commit objects still physically present in .git/objects even if nothing references them, which you can inspect with git show <hash> and recover the same way with git branch.

It's also worth understanding when a detached HEAD state is actually the correct, intentional choice rather than a mistake to avoid. Checking out a tag or a specific commit to inspect old code, run a bisect, or build a specific release artifact is a completely normal use case β€” the state only becomes a problem if you forget you're in it and start committing new work you intend to keep. Tools like git bisect, for example, deliberately put you in detached HEAD as it walks through commits looking for the one that introduced a bug:

git bisect start
git bisect bad HEAD
git bisect good v1.0.0

In that context, detached HEAD is expected and safe, since you're not meant to commit new work during a bisect session β€” you're just checking out different historical points to test.

To avoid landing in this state by accident in the first place, get in the habit of using git switch instead of the older, more overloaded git checkout command for day-to-day branch switching. git switch was introduced specifically to separate "switch branches" from "check out a commit or file," and it warns more clearly when you're about to detach:

git switch main
git switch feature/login

Reserve git checkout <commit> for the deliberate cases β€” inspecting history, bisecting, or testing an old release β€” where detached HEAD is exactly what you want.