GitGit

You are in 'detached HEAD' state

HEAD points at a commit instead of a branch, so new commits belong to nothing. How to keep work you made there, and why CI is detached on purpose.

easy fix5 min read

the git error
You are in 'detached HEAD' state. You can look around, make experimental changes and commit them, and you can discard any commits you make in this state without impacting any branches by switching back to a branch.

Warning: you are leaving 1 commit behind, not connected to any of your branches

Do this first3 steps

Run these in order. Each one tells you what its output means before you change anything.

  1. 1

    Confirm you are detached and see where you are

    git status | head -3 && git log --oneline -1

    "HEAD detached at <sha>" confirms it. Note the sha before doing anything else, because it is how you get back if you move away.

  2. 2

    If you made commits here, put them on a branch before leaving

    git switch -c rescue/my-work

    This creates a branch at the current commit, so the work is no longer dangling. Do this before switching branches, or the commits become unreferenced.

  3. 3

    If you already left and lost them, find them in the reflog

    git reflog | head -20

    Every position HEAD has held is recorded for about 90 days, including commits made while detached. Find the sha in the list and git branch rescue <sha> brings it back.

All 9 sections

HEAD normally points at a branch, and the branch points at a commit. Detached means HEAD points straight at a commit, so there is no branch to advance. Commits made here belong to nothing, and switching away leaves them unreferenced.

It is not an error. It is a normal state that becomes a problem only if you commit and then leave.

git status | head -3
HEAD detached at a1b2c3d

How you get here

git checkout a1b2c3d        # a commit
git checkout v1.4.2         # a tag
git checkout origin/main    # a remote-tracking ref

All three detach, because none of them is a local branch. origin/main catches people out: it is a pointer Git updates on fetch, not something you can commit to.

An interactive rebase also detaches while it replays commits, which is why git status looks alarming in the middle of one.

If you have made commits here

Give them a branch before you go anywhere:

git switch -c rescue/my-work

Now they are referenced and safe. From there, merge or rebase as normal.

To confirm what you would be leaving:

git log --oneline main..HEAD

If you already left

Git warns you:

Warning: you are leaving 1 commit behind, not connected to any of your branches:
  a1b2c3d Fix the retry logic

That is easy to scroll past. The reflog remembers anyway:

git reflog
e4f5a6b HEAD@{0}: checkout: moving from a1b2c3d to main
a1b2c3d HEAD@{1}: commit: Fix the retry logic
git branch rescue/my-work a1b2c3d

The reflog keeps unreachable entries for 90 days by default, and reachable ones indefinitely, so this works for anything recent. git config gc.reflogExpireUnreachable controls the window.

If the reflog does not have it:

git fsck --lost-found

That finds dangling commits directly in the object database. Slower, and it works when the reflog has been pruned.

Getting back without keeping anything

git switch -
git switch main

git switch - returns to the previous branch, like cd -.

Why CI is always detached

Nearly every CI system checks out the exact commit that triggered the build, not a branch. That is deliberate: a branch could move between the trigger and the checkout, and the build must be of a specific, reproducible commit.

git branch --show-current
# (empty)

Which is why a pipeline that needs to push has to say where:

git push origin HEAD:main

And why git describe --tags often fails there: actions/checkout and its equivalents default to a shallow clone with no tags.

- uses: actions/checkout@v4
  with:
    fetch-depth: 0     # full history and tags

fetch-depth: 0 is slower on a large repository, so only ask for it where you need history.

Submodules are detached by design

cd vendor/lib
git status
# HEAD detached at a1b2c3d

The parent repository records an exact commit for each submodule, not a branch, so checking one out detaches. Committing inside a submodule without first switching to a branch is a common way to lose work.

cd vendor/lib
git switch main
# make changes, commit, push
cd ../..
git add vendor/lib
git commit -m "Bump vendor/lib"

The parent then records the new commit.

Using it deliberately

Detached HEAD is the right tool for looking at history without disturbing anything:

git switch --detach v1.3.0
make test
git switch -

git switch --detach is explicit about the intent, where git checkout is ambiguous between switching branches and moving to a commit. Since Git 2.23, switch and restore split those two jobs apart, and using them makes scripts clearer.

A checklist

  1. git status. "HEAD detached at" confirms it, and gives you the sha.
  2. Made commits here → git switch -c <branch> before doing anything else.
  3. Already left → git reflog, then git branch <name> <sha>.
  4. Reflog pruned → git fsck --lost-found.
  5. Nothing to keep → git switch - or git switch main.
  6. In CI it is normal. Push with git push origin HEAD:<branch>.
  7. Need tags in CI → fetch-depth: 0.
  8. Inside a submodule, switch to a branch before committing.

Frequently Asked Questions

Is detached HEAD an error?

No, it is a normal state, and Git prints a long explanation rather than an error because of how often it confuses people. It simply means HEAD points directly at a commit instead of at a branch. Looking around, running tests and building at that commit are all perfectly safe. It becomes a problem only if you make commits and then switch away, because those commits have no branch pointing at them and become unreferenced.

How do I keep commits I made while detached?

Create a branch at your current position with git switch -c <name> before switching anywhere else. That gives the commits a reference and they behave like any other branch from then on. If you have already switched away, git reflog lists every position HEAD has held, including those commits; find the sha and run git branch <name> <sha>. Git's warning on leaving also prints the sha, which is worth reading rather than scrolling past.

How long do I have to recover lost commits?

The reflog keeps unreachable entries for 90 days by default, which covers almost every realistic case. After that git gc can prune them, though git fsck --lost-found may still find dangling objects in the repository until they are actually garbage collected. Both defaults are configurable through gc.reflogExpireUnreachable. Note the reflog is per clone and local, so it does not help if you deleted the whole repository directory.

Why is CI always in detached HEAD state?

Because CI checks out the exact commit that triggered the run rather than a branch, which guarantees the build is of a specific, reproducible tree. A branch could move between the webhook firing and the checkout completing, so building "the tip of main" would be ambiguous. The practical consequences are that git branch --show-current is empty, so pushes need git push origin HEAD:<branch>, and that the clone is usually shallow, so history-dependent commands need fetch-depth: 0.

Why is my submodule always detached?

Because the parent repository records a specific commit for each submodule rather than a branch, which is the whole point: it pins the dependency to an exact version. Checking out a submodule therefore lands on that commit with no branch. If you need to change something inside one, git switch to a branch first, commit and push there, then stage the submodule in the parent and commit that to record the new pointer. Committing inside a detached submodule is an easy way to lose work.

Reference and practice

Learn the underlying concept

Other Git errors