Git Detached HEAD: Recover Without Losing Commits
Stay calm and create a branch where you are.
20+ years shipping production infrastructure and CI/CD at scale. Drawn from code that ran under real load.
- ✓Git installed with basic checkout and commit comfort
- ✓A clone where you can detach HEAD without risk (any test repo)
- ✓A shell prompt or alias setup you can tweak to show Git state
- Detached HEAD means HEAD points at a raw commit instead of a branch, usually after checking out a tag, an old commit, or a remote ref
- Anything you commit while detached is real but branchless, so create a branch with git branch -c rescue to keep it safe
- Return to safety with git checkout - or git switch -; your work stays on the rescue branch you just made
- If you already left without saving, run git reflog, find your commit hash, and branch from it before it gets pruned
Picture branches as labeled bookmarks and commits as pages in a notebook. Normally your bookmark sits on the latest page and moves forward as you write. Checking out an old commit pulls the bookmark out and drops you on that page directly — you're reading fine, but new pages you write have no bookmark holding them. They exist, yet nothing points at them. The fix is embarrassingly simple: stick a new bookmark where you stand before you walk away.
You checked out a tag to reproduce a bug, or an old commit to run a bisect, and now your prompt says 'detached HEAD' in alarming colors. Then you fixed the bug, committed twice, and switched back to main — and your commits seem to have vanished into thin air. Take a breath: they're almost certainly still in your repo, reachable through the reflog, waiting for a branch to adopt them.
Detached HEAD just means HEAD points directly at a commit hash instead of at a branch reference. Git enters this state whenever you check out something that isn't a local branch tip: a tag, a historical commit, a remote-tracking ref, or a pull-request ref. In this state everything works — you can edit, stage, and commit — but new commits grow on an unnamed nub that no branch follows. Leave that spot without naming it and the commits look lost.
Recovery is two moves: name your current spot with a branch before you leave, and use checkout - to hop back. If you already left, the reflog recorded every HEAD position, so you can find the hash and branch from it. This guide covers why detachment happens, how to save work in place, how to resurrect abandoned commits, and how to work on old code without getting stranded again.
Why Git Detaches Your HEAD in the First Place
HEAD is a pointer that normally aims at a branch reference like refs/heads/main, and the branch reference aims at a commit. Checking out a branch moves HEAD to the reference, so new commits advance the branch automatically. But tags, raw hashes, and remote-tracking refs aren't branches — there's no reference to advance. When you check one out, Git points HEAD directly at the commit and warns you the HEAD is detached. It's not an error state; it's Git being honest that no branch will follow your next commit.
The usual triggers are all legitimate workflows: git checkout v1.2.3 to reproduce a customer bug on the released code, git checkout 9f3ac2e during a bisect, git checkout origin/main to peek at the server's state, or fetching a pull-request ref to test someone's changes. Each puts you on a specific commit with no branch semantics. Problems start only when you commit in this state and then move away, because the new commits have no name pointing at them.
Notice that Git tells you plainly every time: 'You are in detached HEAD state' plus instructions for keeping your work. The state also shows in git status and in most shell prompts. Detachment is only dangerous when combined with inattention — engineers who read the banner branch in time, and engineers who ignore it learn about the reflog the hard way.
Save Work In Place: Branch Before You Bounce
If you're detached right now and your commits matter, do nothing that moves HEAD. Don't check out main, don't bisect further, don't fetch-and-reset. Run git branch -c rescue-name while standing on your newest commit. That single command creates a branch reference pointing exactly where HEAD points, adopting your whole anonymous chain instantly. Your commits are now ordinary branch commits with a name, a log, and a pushable ref.
From there, return to familiar ground with git checkout - (the dash means 'wherever I was before') or git switch -. Your rescue branch stays behind, intact, holding every commit. Verify with git log --oneline rescue-name, then treat it like any feature branch: rebase it onto main, review it, merge it. Nothing about its detached origin makes it second-class once it has a name.
Prefer git switch -c rescue-name when you also want to move onto the branch immediately — it creates the branch and checks it out in one motion, carrying uncommitted changes along. The habit to drill is ordering: name first, move second. Every recovery story that ends well follows that order, and every story that ends in the reflog skipped it. Once safe, decide calmly whether to merge, rebase, or cherry-pick the rescued commits onto your main line.
Resurrect Abandoned Commits With the Reflog
The reflog is Git's flight recorder: every position HEAD ever occupied, with the command that moved it and a timestamp. Commits you left behind detached still exist as objects, and the reflog entry pointing at them keeps them alive. Run git reflog and scan for your commit messages or the 'checkout: moving from abc1234 to main' line — the hash on the left side of that move is where your work sits.
Once you have the hash, resurrect with git branch rescue-name <hash>. Your commits reappear as an ordinary branch, ready for log, rebase, and push. If you remember nothing but the message fragment, git log --all --grep='nil guard' --oneline can find commits across every ref including the reflog-adjacent ones. For truly desperate cases, git fsck --lost-found recovers dangling commits even after reflog expiry, though you'll be identifying them by content rather than message.
Act with reasonable speed but not panic. Default reflog expiry keeps unreachable entries for 30 to 90 days, so a same-day rescue is trivially safe. Still, don't let 'Git keeps everything' become an excuse to postpone: aggressive gc configs, deleted clones, and fresh checkouts can narrow your options. Branch the moment you find the hash, then breathe.
Checkout Minus: the Fast Way Back to Safety
The dash argument is Git's 'take me back' shortcut: git checkout - (and git switch -) returns to the branch or commit you occupied before the current one. After you've branched your detached work, one dash-hop puts you back on main with your files updated and your rescue branch safely stored. It's the same muscle memory as cd - in the shell, and it eliminates the 'wait, which branch was I on' guessing that causes second mistakes during recovery.
Combine it with verification and the loop closes cleanly: branch -c to save, checkout - to return, branch -vv to confirm both refs exist where you expect. If you hop back and your working tree looks wrong, don't compound it — git status tells you which branch you're on and whether stray changes followed you. Stash or commit them deliberately rather than dragging detached-state edits silently onto main.
Teach the dash to your whole team alongside the rescue habit. Recovery under deploy pressure goes wrong when engineers type branch names from memory at speed. checkout - needs no memory: it always means back, and back is almost always where you want to stand while deciding what to do with the rescued branch. After hopping back, glance at git branch -vv output to confirm both refs sit where you expect before running anything destructive.
Work on Old Code Without Getting Stranded
Most detachment is avoidable with one habit: branch at checkout time. Instead of git checkout v2.14.0, run git checkout -b verify-bug v2.14.0. You land on the identical code, but on a real branch that follows your commits, pushes cleanly, and opens pull requests. Delete it when done. The cost is one flag; the benefit is never needing rescue in the first place.
For read-only missions, staying detached is genuinely fine — bisect runs, test archaeology, diffing two releases. The rule is intent: if there's any chance you'll commit, branch first. git worktree offers an even cleaner option for parallel old-code work: git worktree add ../hotfix-214 v2.14.0 gives the old code its own directory and branch while your main checkout stays put, eliminating the whole leave-and-return dance.
Remote-ref peeking deserves the same treatment. git checkout origin/main detaches by design, since the remote-tracking ref isn't yours to advance. If the peek turns into work, switch -c immediately. Engineers who treat every checkout-of-a-non-branch as 'branch or read-only, decide now' simply stop producing stranded commits. Add the rule to onboarding docs so new hires learn it before their first tag checkout, not after their first rescue.
Make Detachment Unmissable in Your Environment
Humans miss banner text under pressure, so make the state visually loud. Shell prompt plugins (starship, powerlevel10k, git-prompt) can render detached HEAD in red with the short hash, turning every terminal glance into a status check. The team in this article's incident added exactly that, and it has caught four near-misses since — engineers seeing red DETACHED before committing, branching first, and never entering recovery at all.
Pair the prompt with a pre-commit nudge for extra safety: a hook that warns when HEAD is detached still permits intentional commits but forces acknowledgment. Some teams go further and alias checkout of tags to auto-create branches, though explicit habits beat magic — an engineer who understands detachment handles novel cases, while an alias only handles the ones it was written for.
Document the two-command rescue in your runbook where on-call eyes can find it at night: branch -c to save, reflog to resurrect. Incidents compress working memory, and a runbook entry turns 'I vaguely remember a blog post' into copy-paste recovery. The goal isn't just surviving detachment; it's making the state so visible and the rescue so rehearsed that it stops costing you deploys.
Two Hotfix Commits Vanished Before a 9 PM Deploy Freezing 14 Engineers
- Detached HEAD commits are real commits with no address. Name your spot with git branch -c before leaving it, and a scary state becomes a two-second save. Make 'branch before you bounce' a reflex for the whole team.
- Checkout output deserves a glance, not a skim. Git announces detachment explicitly every time, but engineers trained to ignore command output walk past it. Read the first three lines of every checkout, especially when a tag or hash is involved.
- A prompt that shows Git state turns invisible danger visible. The red DETACHED indicator added after this incident has since caught four near-misses before any commit was made. Environment design beats memory every time.
| File | Command / Code | Purpose |
|---|---|---|
| save-detached-work.sh | git status | head -3 | Save Work In Place |
| reflog-rescue.sh | git reflog | head -20 | Resurrect Abandoned Commits With the Reflog |
| hop-back-safely.sh | git branch -c rescue-nil-guard | Checkout Minus |
| branch-old-code.sh | git checkout -b verify-bug v2.14.0 | Work on Old Code Without Getting Stranded |
| detached-guardrails.sh | git config --global status.short true | Make Detachment Unmissable in Your Environment |
Key takeaways
Common mistakes to avoid
6 patternsSwitching back to main before naming the detached spot
Committing a 'quick fix' onto a bisect checkout
Assuming a tag checkout behaves like a branch
Deleting the clone or resetting before checking the reflog
Force-pushing the rescue branch over main to 'put the fix back'
Ignoring the detached HEAD banner in checkout output
Interview Questions on This Topic
What does detached HEAD mean, and how do you get into that state?
Frequently Asked Questions
20+ years shipping production infrastructure and CI/CD at scale. Drawn from code that ran under real load.
That's Git. Mark it forged?
5 min read · try the examples if you haven't