I Committed in Detached HEAD — Now What?

Made commits in detached HEAD and they vanished after switching branches? Find the SHA in the reflog and attach it to a branch before git gc prunes it.

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 typeConfig keyDefault
Reachable from a branch tipgc.reflogExpire90 days
Unreachable (your dangling detached-HEAD commits)gc.reflogExpireUnreachable30 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 nowYou are inJump to
Still in the session, git status says HEAD detached at ...Not switched away yetStep 2, the easy case
Switched away, commits gone from git logDangling but in reflogStep 1 then Step 2
git reflog shows nothing useful, you ran git gcReflog prunedFAQ: 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 switch instead of git checkout. git switch refuses 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.1 for a real branch.
  • Always run git status before committing to confirm the first line names a branch, not HEAD detached at ....
  • After git bisect, always run git bisect reset to 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_ID so any commits a job makes are on a named ref. With actions/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 run git status and confirm HEAD is on a branch before committing.
  • Give yourself a wider safety net: git config --global gc.reflogExpireUnreachable 90 extends 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.

Tags: #git #version-control #Troubleshooting