You ran git checkout v2.3.1 to investigate an old tag, made a few experimental commits, then ran git checkout main — and Git printed Warning: you are leaving N commits behind, not connected to any of your branches. Your new commits are not on main, not on any branch, and git log no longer shows them. They are dangling commits: live objects in the object database with no branch reference pointing to them. Nothing is deleted yet — the commits still exist and the reflog still points at them — so recovery is almost always successful if you act soon.
Fastest fix: the warning Git just printed includes the SHA. Run git branch rescue/detached-work <SHA> to pin those commits to a real branch, then merge or cherry-pick from rescue/detached-work. If you already cleared the terminal, the SHA is in git reflog. Details below.
How long do you actually have?
This trips people up because Git has two different expiry defaults (as of June 2026, Git 2.x):
| Reflog entry type | Config key | Default |
|---|---|---|
| Reachable from a branch tip | gc.reflogExpire | 90 days |
| Unreachable (your dangling detached-HEAD commits) | gc.reflogExpireUnreachable | 30 days |
Detached-HEAD commits you abandoned are unreachable, so the 30-day number is the one that applies — not 90. After that window, the next git gc (or any git gc --auto, which can fire silently after routine commands) can prune them permanently. Treat 30 days as the deadline and recover today.
Common causes
Ordered by hit rate, highest first.
1. Checking out a tag or SHA puts you in detached HEAD
git checkout v2.3.1 or git checkout abc1234 both detach HEAD. There is no active branch, so any commits you make are orphaned the moment you switch elsewhere.
How to spot it: Before switching away, run git status — the first line reads HEAD detached at v2.3.1 rather than On branch feature.
2. git bisect left you in detached HEAD after the session
git bisect start / git bisect good / git bisect bad operates in detached HEAD mode. If you committed a workaround to verify a fix during bisect and forgot to run git bisect reset, those commits are dangling.
How to spot it: git bisect log still shows an active bisect session, or .git/BISECT_LOG exists.
3. Checking out a remote-tracking branch directly
git checkout origin/feature checks out the remote-tracking ref, not a local branch, causing detached HEAD. New commits are not pushed anywhere and have no local branch reference.
How to spot it: git status reads HEAD detached at origin/feature instead of a branch name. (Use git checkout feature without the remote prefix to create and track a local branch instead.)
4. CI/CD pipeline checked out a specific SHA
Automated pipelines (GitHub Actions, Jenkins, GitLab CI) often clone and git checkout <sha> for reproducibility. GitHub’s actions/checkout in particular leaves the runner at a detached SHA, and on pull_request events it checks out a temporary merge commit. A step that commits inside that workspace produces dangling commits that never reach a branch.
How to spot it: cat .git/HEAD shows a raw SHA rather than ref: refs/heads/<branch>; the CI log shows HEAD is now at <sha> with no branch name.
5. git stash pop after a checkout moved to an unexpected state
Some older workflows stash, checkout a SHA, pop the stash, make edits, commit — all in detached HEAD without realizing the stash pop did not re-attach HEAD to a branch.
How to spot it: git reflog | head -5 shows stash@{0}: pop immediately before a series of commits with no branch-switch entry between them.
Which bucket am I in?
| Symptom right now | You are in | Jump to |
|---|---|---|
Still in the session, git status says HEAD detached at ... | Not switched away yet | Step 2, the easy case |
Switched away, commits gone from git log | Dangling but in reflog | Step 1 then Step 2 |
git reflog shows nothing useful, you ran git gc | Reflog pruned | FAQ: git fsck --lost-found |
Shortest path to fix
Step 1: Find the SHA of your detached-HEAD commits
If you have not switched away yet, git log --oneline shows them at the top. Note the topmost SHA and skip to Step 2.
If you already switched away, the reflog still records every position HEAD held:
git reflog --date=relative | head -20
Look for the line that reads commit: <your message> under the detached HEAD session. The short SHA in the left column is your most recent detached commit. You will also see a checkout: moving from <sha> to main line — the <sha> it moved from is the tip of your lost work.
Step 2: Create a branch pointing to those commits immediately
# If you are still in detached HEAD
git branch rescue/detached-work HEAD
# If you have already switched to another branch
git branch rescue/detached-work <SHA-from-reflog>
git branch (not git switch/git checkout) is deliberate here: it creates the rescue ref without moving your working tree, so you cannot lose more state while you sort things out.
Step 3: Verify the branch has your work
git log --oneline rescue/detached-work | head -10
git diff main..rescue/detached-work -- src/
Confirm your commits and your changes are present before going further.
Step 4: Choose how to integrate the commits
Option A — merge into your target branch (keeps history as-is):
git checkout main
git merge rescue/detached-work --no-ff -m "merge: restore detached-HEAD work"
Option B — cherry-pick individual commits (when you want only some):
git checkout main
git cherry-pick rescue/detached-work~2..rescue/detached-work
Option C — rebase onto the target, then fast-forward (linear history):
git checkout rescue/detached-work
git rebase main
git checkout main
git merge rescue/detached-work --ff-only
Step 5: Confirm it’s fixed, then push and clean up
# the rescued commits are now reachable from main
git branch --contains <SHA-from-reflog> # should list main
git log --oneline -5 main # your commits at the top
git push origin main
git branch -d rescue/detached-work # safe: -d refuses if unmerged
git branch --contains <SHA> listing main is the definitive “it’s fixed” check: it proves the commit is now reachable from a real branch and is no longer a garbage-collection target.
Prevention
- Use
git switchinstead ofgit checkout.git switchrefuses to detach unless you pass--detach, so you can no longer drift into detached HEAD by accident. To look at an old tag,git switch --detach v2.3.1; to commit on it,git switch -c investigate/v2.3.1 v2.3.1for a real branch. - Always run
git statusbefore committing to confirm the first line names a branch, notHEAD detached at .... - After
git bisect, always rungit bisect resetto return to your original branch before committing anything. - Show the current branch (and detached-HEAD state) in your shell prompt. oh-my-zsh,
starship, and bash-git-prompt all flag detached HEAD distinctly out of the box. - In CI, immediately attach a branch after checkout:
git switch -c ci/run-$BUILD_IDso any commits a job makes are on a named ref. Withactions/checkout, this is the simplest way to avoid dangling CI commits. - If you work with AI coding agents, add a line to
CLAUDE.md/AGENTS.md: the agent must rungit statusand confirm HEAD is on a branch before committing. - Give yourself a wider safety net:
git config --global gc.reflogExpireUnreachable 90extends the unreachable-commit window from 30 to 90 days.
FAQ
Q: How long before the garbage collector deletes my dangling commits?
A: By default, unreachable commits leave the reflog after 30 days (gc.reflogExpireUnreachable = 30), at which point git gc can prune them. The 90-day figure you may have seen is gc.reflogExpire, which only covers entries still reachable from a branch — it does not apply to abandoned detached-HEAD work. Recover within 30 days; sooner is safer because git gc --auto can run on its own.
Q: I already ran git gc and the reflog entry is gone. Is the work lost?
A: Maybe not. Run git fsck --lost-found (or git fsck --full --no-reflogs --unreachable). Git writes dangling commit, tree, and blob objects to .git/lost-found/commit/. Inspect each with git show <sha> to find your work, then git branch rescue <sha> on the right one.
Q: Can I push from a detached HEAD directly?
A: Yes, with an explicit refspec: git push origin HEAD:refs/heads/new-branch-name. That creates new-branch-name on the remote pointing at your current detached SHA — a one-line escape hatch when you cannot create a local branch first.
Q: Is detached HEAD ever intentional?
A: Yes. git bisect, read-only exploration of historical states, submodule checkouts, and some CI reproducibility flows all use detached HEAD on purpose. It is only a problem when you commit new work without first creating a branch.
Q: How do I stop committing in detached HEAD at all?
A: A pre-commit hook can block it. In .git/hooks/pre-commit add: git symbolic-ref -q HEAD >/dev/null || { echo "Refusing to commit in detached HEAD"; exit 1; }. git symbolic-ref HEAD fails when HEAD is detached, so the commit is aborted.