Git

What Is a Detached HEAD in Git?

What Is a Detached HEAD in Git?

Git lets you stand on any commit in history and keep working, which is fine until you notice HEAD has stopped following a branch. That is a detached HEAD: HEAD points straight at a commit instead of a branch, so whatever you commit there is recorded on no branch. You get there by checking out a commit hash, a tag, or a remote-tracking branch like origin/main.

You don’t even have to move. According to the git-checkout manual (Git 2.55, 2026), git checkout –detach with no argument detaches HEAD at the tip of the current branch. So the state belongs to HEAD, not to the commit you happen to be standing on.

Whether a visit costs you anything comes down to one condition. Any new commits need a branch or some other reference before you switch away.

What Is a Detached HEAD in Git?

YouTube player

HEAD points directly at a commit here, not at a branch reference, and commits made in that state are recorded on no branch.

Git doesn’t treat this as an error. It was built for looking at old versions and running throwaway experiments (git-checkout manual, Git 2.55).

How HEAD Works When It Is Attached

YouTube player

Normally HEAD refers to a named branch, and the branch refers to a commit.

Git stores that as a symbolic ref, so the file .git/HEAD holds a line like ref: refs/heads/master, according to the git-symbolic-ref manual.

Each new commit moves the branch forward, and HEAD follows the branch along. The branch itself is just a name that stores one commit hash, which is why it’s the thing that actually records your work.

What Changes When HEAD Is Detached

YouTube player

HEAD now holds a raw commit hash, with no ref: line. New commits are referenced by HEAD and nothing else, and every existing branch stays put at its old tip.

The Git manual walks through a history with tag v2.0 on commit b and master on commit d. Check out the tag, commit twice, and commits e and f hang off b with only HEAD pointing at them.

Why is GitHub the heart of open source?

Uncover GitHub statistics: developer community growth, repository trends, collaboration patterns, and the platform that powers modern software development.

Explore GitHub Data →

What Puts Git Into Detached HEAD State?

YouTube player

Check out anything that isn’t a local branch and HEAD detaches. That means a commit hash, a tag, a remote-tracking branch, or anything passed to checkout with –detach.

The rule sits in the git-checkout manual. If the name resolves to a valid ref once refs/heads/ is put in front of it, Git checks out the branch. If it doesn’t, but it names a valid commit, HEAD detaches.

So a plain git checkout of a hash or a tag drops you here.

ActionExampleIntent
Checkout a commit hashgit checkout 8fd3350Usually deliberate
Checkout a taggit checkout v2.0Usually deliberate
Checkout a remote-tracking branchgit checkout origin/mainOften accidental
Explicit detachgit switch –detach HEAD~3Always deliberate
Interactive rebasegit rebase -i HEAD~3Temporary, handled by Git

The remote-tracking row is the one that catches people. Typing git checkout main creates a local main from origin/main when exactly one remote carries that name. Typing origin/main detaches.

A Git tag detaches for a similar reason, since it’s a fixed pointer and not a line of work that moves forward.

Looking to sharpen your Git skills? Branching, merging, rebasing, and everything else you need - including git rebase, git stash, and commit workflows - is on one page in the Git Cheat Sheet.

Rebase only detaches for a while. Per the git-rebase manual, it checks out the upstream with the equivalent of git checkout –detach, replays your commits one at a time, and then points your branch at the final commit.

If you run git rebase –abort, HEAD goes back to the original branch.

git switch showed up in Git 2.23.0 (August 2019). Its synopsis takes a branch name by default and wants –detach before it will land on a commit, so fewer people detach by accident these days.

How Can You Tell Whether HEAD Is Detached?

YouTube player

Each of the usual commands reports it a little differently.

  • git status prints “HEAD detached at” plus a short hash on its first line, where an attached HEAD reads “On branch”.
  • git branch lists the detached HEAD at the top, ahead of your local branches (git-branch manual).
  • git branch –show-current prints nothing at all.
  • git symbolic-ref -q HEAD exits with status 1 and says nothing.

Of those, git status is the one worth running before every commit. Committing while that first line says detached is exactly the case the next section is about.

Scripts need a bit of care with symbolic-ref. The manual documents exit status 0 for success, 1 when the name isn’t a symbolic ref, and 128 for any other error, so test for 1 specifically.

For machine-readable output, git status –porcelain=v2 –branch prints the header line # branch.head (detached).

One trap. The warning Git prints at checkout is only advice, and the advice.detachedHead setting switches it off (git-config manual). A quiet checkout doesn’t prove HEAD is attached.

GUI clients show the state too. GitHub Desktop swaps the Current Branch label for “Detached HEAD” and the commit SHA (GitHub Docs).

Do Commits Made in Detached HEAD Get Lost?

YouTube player

Not by themselves. Only commits that have no branch, tag or other reference are at risk, and only once HEAD has moved away. Git’s routine garbage collection deletes them eventually (git-checkout manual).

The manual’s example makes it concrete. After checking out master again, nothing refers to commit f, so f and its parent e become garbage. Create a reference while HEAD still sits on f and that can’t happen.

If you made no commits while detached, there’s nothing to lose and switching back is routine.

Once HEAD has moved, the commits still exist for a while. You need their hash, and the reflog is where it lives.

Guides don’t agree on how long that window lasts. A 2025 Medium guide gives a loose 30 to 90 days, while the Git manual splits the timeline across separate defaults (git-gc manual, unchanged through Git 2.56, September 2026).

  • gc.reflogExpire is 90 days, and it applies to reflog entries still reachable from the current tip.
  • gc.reflogExpireUnreachable is 30 days, and it applies to entries that can’t be reached from the current tip.

On top of those, gc.pruneExpire gives unreachable objects 2 weeks of grace before git gc prunes them.

An abandoned detached commit can’t be reached from where HEAD points now, so its reflog entry expires after 30 days. The 90-day figure covers entries on history you still hold.

The same manual says gc keeps any object a reflog references, which makes the entry’s expiry the real deadline. Treat 30 days as the working limit.

Set gc.reflogExpireUnreachable to now and the next cleanup expires those entries immediately. That removes the safety net completely, so don’t.

How to Leave Detached HEAD

YouTube player

It depends on what you did while detached. If nothing is worth keeping, switch away. If there are commits you want, create a branch before you leave.

SituationCommandResult
No commits madegit switch mainHEAD reattaches to main
Commits to keep, still detachedgit switch -c rescueNew branch at this commit, HEAD attached
Commits to keep, stay detached a moment longergit branch rescueBranch created, HEAD stays detached
Uncommitted edits in the waygit switch -m mainEdits stashed, then reapplied on main

Going back to where you were is even quicker. Plain switching branches with git switch – jumps to the last branch or commit you were on (git-switch manual).

The two rescue commands differ in one way. git switch -c creates the branch and moves you onto it in a single step, while git branch leaves you detached. The git-checkout manual says the same about git tag.

git switch -c is also the safer one, since Git leaves the branch uncreated if the switch itself fails.

Uncommitted edits need a decision before you move. Run git stash first, or pass -m and let Git stash and reapply them for you.

A few ways this goes wrong. git switch refuses to run if the move would lose local changes, unless you add –discard-changes, which throws them away. With -m, a conflict on reapply leaves the entry on the stash list until you resolve it and run git stash drop. And -C instead of -c resets an existing branch to the start point, so stick with -c for rescue branches.

How to Recover Commits After Switching Away

Read the reflog for the hash of your last detached commit, then point a new branch at it. Both steps work as long as the reflog entry is still alive.

  1. Run git reflog. Entries that start with “commit:” are work you did while detached, and entries that start with “checkout: moving from” mark each switch.
  2. Copy the hash of the newest detached commit. Its earlier commits come along with it.
  3. Run git switch -c rescue abc1234, where abc1234 is the copied hash. HEAD lands on a new branch holding all of that work.
  4. Confirm with git log that the commits are there.
  5. Want a single commit instead? Switch to your working branch and run git cherry-pick with the hash.

The git-checkout manual shows two equivalent views of recent HEAD positions, git reflog -2 HEAD and git log -g -2 HEAD. If you already live in git log, the -g flag walks the same reflog.

Step 3 is the same move you make when you create a new branch in Git on any other day. The only difference is that the start point is a hash instead of HEAD.

If the reflog comes up empty because the entry expired, git fsck –lost-found writes dangling commits into .git/lost-found/commit/, provided gc hasn’t pruned them yet (git-fsck manual). And if you have no hash to go on, git fsck –unreachable prints every object that exists but hangs off no reference.

When Is Detached HEAD the Right State?

YouTube player

Use it when the result shouldn’t leave a branch behind. Reading history, bisecting, building a tagged release and running a pinned checkout in a pipeline all fit, and a branch name would only be clutter in any of them.

Inspecting and Testing Old Commits

git bisect checks out a commit from the middle of your good-to-bad range at every step, and git bisect reset returns the tree to whatever was checked out before the session began.

The Git 2.56 manual also documents –reset-when-found, which takes original or found and can leave the first bad commit checked out.

For release builds, a tag names exactly one commit, so a build from a tag checkout sees precisely the files that were tagged.

And if you just want somewhere to poke around, git worktree add -d with a path creates a second working directory with a detached HEAD at the same commit as the current branch (git-worktree manual). HEAD is per-worktree, so your main checkout stays on its branch the whole time.

CI Pipelines and Submodules

The actions/checkout README (v7.0.1, July 2026) has a few facts every pipeline author runs into. Default fetch-depth is 1, so only the triggering commit is present. pull\_request runs start detached, so pushing back needs ref set to the head branch. And git checkout HEAD^ needs a fetch-depth of 2.

For continuous integration jobs that only build and test, leave HEAD detached. Those jobs need the triggering commit, not a branch.

Submodules are detached by design. The superproject records the exact commit its submodule should sit at in a gitlink entry, and a recursive checkout detaches the submodule’s HEAD at that commit (gitsubmodules and git-checkout manuals).

Commits made inside a submodule follow the same rules as anywhere else. Create a branch there first, commit, then record the new commit in the superproject.

Linked worktrees won’t save you here. The git-worktree manual advises against multiple checkouts of a superproject and calls its submodule support incomplete.

How to Avoid Detaching HEAD by Accident

YouTube player

Work from branch names. Hashes, tags and remote-tracking names are what detach HEAD, so give them a branch before you commit anything on top.

Start from a tag or hash with git switch -c hotfix v2.0 and the branch exists from the very first commit. When you do want a detached HEAD, write git switch –detach, which turns an accident into a decision. If two remotes carry the same branch name, set checkout.defaultRemote to origin so a bare name resolves there.

That remote setting lives in git config, per repository or globally.

Rebase is the other trap. The git rebase manual says it aborts when you are on no branch and name no upstream.

The habit costs a few keystrokes. A trip through the reflog costs more.

Detached HEAD FAQ

Why does git push fail from a detached HEAD?

Plain git push sends the current branch, and a detached HEAD doesn’t have one. With the default push.default=simple, Git pushes to a remote branch of the same name as the current branch, so there’s no name to use (git-push manual).

How do I push commits made in a detached HEAD without creating a branch?

Name the destination yourself with git push origin HEAD:branch-name. The source side of a refspec accepts any commit expression, including HEAD, but the destination has to be the name of a ref, such as a branch or tag.

Pushing to an existing branch still has to be a fast-forward.

Why does Git allow a detached HEAD at all?

HEAD has to be able to name any commit, including ones no branch follows, like tagged releases and older history. The git-checkout manual adds that every normal operation still works in this state, so you can edit, add and commit as usual.

Should I use git checkout or git switch?

Use git switch for moving between branches. git checkout does two jobs, switching branches and restoring file versions, which is why its manual needs a section on argument disambiguation.

For the file job, the same manual points to git restore.

Moving Rescued Commits Into Main Without Rewriting History

Merging the rescue branch into main carries your detached HEAD commits over with their hashes intact and rewrites no history. Git fast-forwards when main is an ancestor of the rescue tip and adds a merge commit when it isn’t (git-merge manual).

To merge the two branches, switch to main and run git merge rescue.

Merge the whole rescue branch first. Cherry-pick only when you want a subset, and delete rescue with -d last.

Cherry-picking copies commits as new ones with new hashes, the same replay the git-rebase manual compares it to, so the originals stay on the rescue branch.

git branch -d refuses to delete a branch that isn’t fully merged into its upstream, or into HEAD when no upstream is set. A rescue branch normally has no upstream, so with main checked out the check runs against main. That makes it a free test that main holds everything. This order holds as of the Git 2.56 manuals (September 2026).

Bogdan Sandu

Stay sharp. Ship better code.

Every week: one curated article, one tool worth knowing, one tip you can use tomorrow. No noise, no padding.