skip to content

In Git, what is a detached HEAD and how do you rescue commits made in one?

level: middleimportance: must knowfreq 68%

answer

  1. Where does HEAD point when it is attached?
  2. A symbolic ref versus a raw hash
  3. Commits advance nothing at all
  4. Give the tip a name before leaving
  5. The reflog remembers every HEAD value

basics

~20 s

HEAD is detached when it names a commit directly instead of a branch, so commits you make advance nothing. Rescue them by creating a branch at that commit — git switch -c <name> before leaving, or git branch <name> <hash> from the reflog afterwards.

solid answer

~50 s

Normally `HEAD` is a symbolic reference to a branch such as `refs/heads/main`, and committing moves that branch forward. When you check out a raw commit, a tag, or a remote-tracking ref, `HEAD` instead holds the commit hash itself — that is detached `HEAD`. You can work and commit normally, but each commit only updates `HEAD`; no branch name follows you, so as soon as you switch away those commits have nothing pointing at them and eventually become garbage-collection candidates. The fix while you are still there is `git switch -c rescue`, which creates a branch at the current commit and attaches `HEAD` to it. If you already left, `git reflog` shows the hashes `HEAD` visited; `git branch rescue <hash>` brings the work back. Detached `HEAD` is also a normal transient state during bisect and rebase, so it is not an error in itself.

code

bash · 7 lines
bash
git checkout 9f2b6c1        # HEAD now holds a hash, not a branch
git status                  # HEAD detached at 9f2b6c1
git branch --show-current   # prints nothing

# ... you commit twice here; no branch moved ...

git switch -c rescue        # names the work and reattaches HEAD

go deeper

for a junior

Recognize the HEAD detached at … message, know it means you are on a commit rather than a branch, and remember git switch -c <name> as the way to keep anything you committed there.

for a middle

Explain the mechanism: HEAD as a symbolic ref versus a raw hash, why commits advance nothing, and how the reflog lets you recover a tip hash after you have already switched away.

for a senior

Show fluency diagnosing it in the wild — reading the reflog to reconstruct what happened, knowing bisect and rebase park you there deliberately, and knowing CI checkouts by hash are detached by design.

for a principal

Own the guardrails: prefer git switch so detaching is explicit, keep advice.detachedHead on for the team, and set reflog and gc expectations so a routine recovery never turns into lost delivery.

## Attached versus detached `HEAD` is the answer to "where am I?". In the attached state it is a *symbolic* ref: its content is a pointer to a branch, e.g. `ref: refs/heads/main`. In the detached state it holds a commit hash directly. The difference shows up only when you commit. Attached, a commit writes the new object and then advances the branch that `HEAD` names, so `HEAD` follows along indirectly. Detached, the commit is written and `HEAD` is repointed at it, but **no branch moves**. You are building a chain of commits that only one thing references — and that thing moves away the moment you check out anything else. ## How you get there - `git checkout <commit-hash>` - `git checkout v1.4` (a tag) - `git checkout origin/main` (a remote-tracking ref, which is not a local branch) - `git switch --detach <commit>` (the explicit, intentional form) - Automatically during `git bisect` and while an interactive rebase is replaying commits Git prints an advice block when it happens, controlled by the `advice.detachedHead` configuration key. `git switch <commit>` deliberately refuses without `--detach`, which removes the most common accidental route. ## Detecting it - `git status` opens with "HEAD detached at …" instead of naming a branch. - `git branch --show-current` prints nothing. - `git rev-parse --abbrev-ref HEAD` prints the literal string `HEAD`. - `git branch` shows a `(HEAD detached at <hash>)` entry at the top of the list. Scripts and prompts often check the second or third of these, because "empty branch name" is exactly the condition you want to react to in CI, where a checkout by hash is routine. ## Rescuing work: still there The cheapest possible fix, before you switch away, is `git switch -c rescue`. This creates `refs/heads/rescue` at the current commit and attaches `HEAD` to it. From this moment the commits are named and safe, and everything behaves normally again. `git branch rescue` (without switching) also names the commits, but leaves `HEAD` detached — fine, as long as you remember the branch now holds them. ## Rescuing work: already switched away Git records every value `HEAD` has taken in its reflog, and that survives your switching away. `git reflog` lists the hashes with the operation that produced each one, and `git branch rescue <hash>` recreates a branch at the tip you find there. The commit you name brings its whole ancestry with it, so recovering the tip recovers the chain. `git log --walk-reflogs HEAD` shows the same information in log form. If reflog entries have already expired, `git fsck --lost-found` can still surface dangling commits, though that is a blunter tool. ## Why the state exists at all Detached `HEAD` is not a bug or a corrupted repository. It is what "check out this exact snapshot without claiming to be on a branch" has to mean in a model where a branch is a moving label. Useful, deliberate uses include: - Inspecting or building an old release tag without creating throwaway branches. - Bisecting: `git bisect` parks you at each candidate commit, and no branch should move while it does. - Rebase replay: each replayed commit is written detached, and the branch is only repointed at the end. - CI checkouts by hash, where naming a branch would be meaningless. ## The failure mode in one sentence The danger is not being detached; it is **committing** while detached and then switching away without naming the work. That is why the habit worth building is: the instant you realize you have committed on a detached `HEAD`, run `git switch -c <name>` before running anything else. ## What it is not It is not a merge conflict state, not a repository-level setting, and not something you "fix" with a reset. `git switch main` (or `git checkout main`) simply reattaches `HEAD` — and silently orphans anything you committed, which is exactly the sequence people describe as "Git ate my commits".

  • How long do commits made on a detached HEAD survive if you never name them?
    Until they are unreachable *and* garbage collection prunes them. Reachability includes reflog entries, which have their own expiry, so there is normally a comfortable window. Do not rely on it: `git gc` can run automatically after ordinary commands, so name the commits as soon as you notice.
  • Why does checking out origin/main leave you detached?
    `origin/main` is a remote-tracking ref, not a local branch, and Git will not let a local commit advance it — its value is owned by fetch. Checking it out therefore detaches HEAD. To work from it you create a local branch, for example `git switch -c main origin/main`.
  • Is a detached HEAD ever the right state to be in on purpose?
    Yes. Inspecting or building a tag, bisecting, and CI checkouts by commit hash are all legitimately detached. The state is only a problem when you commit in it and walk away without creating a ref.
  • How can a script tell it is on a detached HEAD?
    `git branch --show-current` prints an empty string, and `git rev-parse --abbrev-ref HEAD` prints the literal `HEAD`. Either check is reliable; `git symbolic-ref -q HEAD` failing is the plumbing-level equivalent.

An attached HEAD is a bookmark clipped to a moving tab; detached, you are holding your finger on a page with no tab. Turn the page and nothing marks where you were.

saying these in an interview costs you the question

  • Says a detached HEAD means the repository is broken
  • Thinks you cannot commit while detached
  • Believes switching back to a branch keeps the detached commits
  • Says the work is permanently lost once you switch away
  • Confuses detached HEAD with being behind the remote

context