Git: you are in 'detached HEAD' state
Hi, it's BlueByte. "Detached HEAD" reads like a crash, but nothing is broken — Git is just telling you HEAD is pointing straight at a commit instead of a branch. Let's understand how you got here, and the one move that keeps any commits you made from getting lost.
What "detached HEAD" means
Git warns after a checkout:
Note: switching to 'a1b2c3d'.
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.Nothing is broken. HEAD is just pointing straight at a commit rather than at a branch that moves with your commits. It is a normal, useful state for inspecting history — the only real risk is committing here and then leaving without saving the work to a branch.
How you ended up detached
You checked out something that is not a local branch. In practice that means one of:
- A commit hash — inspecting an old state.
- A tag — checking out a release like
v1.4.0. - A remote-tracking branch like
origin/maininstead of a localmain. - A submodule, which Git keeps at a specific commit by design.
- A
git bisectrun, which checks out commits directly while it searches.
In that state new commits are recorded, but no branch label follows them, so switching away leaves them unreferenced.
Ask Git where HEAD points
You do not have to wonder — ask Git:
git status
git symbolic-ref -q HEAD || echo "HEAD is detached"git status says "HEAD detached at a1b2c3d" and names what you checked out. If it names a tag or a remote branch, that is how you got here — checking out a local branch name never detaches HEAD. git log --oneline -1 shows the exact commit you are sitting on.
Keep your work: put a branch on it
- If you made commits here and want to keep them, create a branch at your current position — the important step:
git switch -c my-workYour commits are now on my-work and safe.
- If you only looked around, switch back to a real branch:
git switch main- If you already switched away and think you lost commits, they are still in the reflog:
git reflog
git switch -c recovered a1b2c3dA real case: two commits on a tag
You check out a tag to reproduce a bug — git checkout v1.4.0 — and Git warns about detached HEAD. You make two commits trying a fix, then run git switch main out of habit. Git says you are leaving commits behind. You realize the work is unreferenced, so you run git reflog, find the top commit hash from a minute ago, and run git switch -c bugfix-1.4 <hash>. Your two commits are now on bugfix-1.4, safe and ready to push. Nothing was lost — the reflog remembered where HEAD had been.
Confirm you're back on a branch
Check HEAD now points at a branch and your work is on it:
git status # "On branch my-work"
git log --oneline -3Seeing "On branch ..." instead of "HEAD detached" means you are back on a branch that will hold future commits.
Keep it from surprising you
When you check out a commit or tag to experiment, create a branch first if you intend to commit. Detaching on purpose to inspect a release is fine — just run git switch -c before you leave if you made changes.
How this differs from a merge error
Detached HEAD is a state, not an error — Git still works normally. It is unrelated to a merge conflict or refusing to merge unrelated histories; those are about combining branches, while this is simply about which ref HEAD is attached to. If you committed here, branch before you leave and nothing is lost.
Related questions
Did I lose my commits when I switched branches?
Almost certainly not. Run git reflog to find the commit hash, then git switch -c <name> <hash> to put a branch on it. The reflog keeps unreachable commits for about 30 days by default.
How did I end up detached?
You checked out a commit hash, a tag, or a remote-tracking branch (origin/...) instead of a local branch. Checking out a local branch name never detaches HEAD.
Is it safe to just switch back and lose the detached commits?
Only if you truly do not want them. Once you switch away they are unreferenced and will eventually be garbage-collected. Create a branch first if there is any doubt.
Why does git checkout <tag> detach but git checkout <branch> not?
A tag is a fixed pointer to one commit; there is no branch to move as you commit, so HEAD attaches directly to the commit. A branch is a moving pointer, so HEAD attaches to it.
git bisect left me in detached HEAD.
That is expected — bisect checks out commits directly while searching. Run git bisect reset when you are done to return to the branch you started from.
References
Haneul Seo
Infrastructure engineer · 10+ years running Linux fleets
More in this category
Git: fatal: detected dubious ownership in repository
Since the CVE-2022-24765 fix in Git 2.35.2, Git refuses to read a repository whose working tree or .git directory is owned by a different user than the one running the command. It shows up in containers, CI jobs, sudo sessions and shared drives. Fix the ownership if the repo should be yours, or add the exact path to safe.directory in your global config — never in the repo's own config, which Git ignores for this.
Kubernetes: Internal error occurred: failed calling webhook
An admission webhook sits in front of your write, the API server could not get an answer out of it, and failurePolicy: Fail turned that silence into a rejection. The tail of the message is the whole diagnosis: context deadline exceeded means the call went nowhere, no endpoints available means nothing is running, and an x509 line means the API server does not trust the webhook's certificate. Each has a different fix, and none of them is your manifest.
MySQL: ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
A statement waited the full innodb_lock_wait_timeout for a row lock another transaction is still holding, and gave up. sys.innodb_lock_waits names the blocking session and hands you the KILL statement, and a blocking_query of NULL means the blocker is idle on an open transaction. The detail most retry loops get wrong: by default only the timed-out statement is rolled back, so your transaction is still open and still holds every lock it took earlier.
Redis: MISCONF Redis is configured to save RDB snapshots, but it's currently unable to persist to disk
Reads keep working and every write is rejected, because the last background save failed and stop-writes-on-bgsave-error defaults to yes. The log names the real cause — no space, a dir the redis user can't write, a read-only mount at rename time, or fork failing with Cannot allocate memory. Fix the cause, run one BGSAVE, and writes come back on their own with no restart: rdb_last_bgsave_status flips from err to ok. Setting stop-writes-on-bgsave-error no restores writes instantly but leaves the snapshot broken, so treat it as a deliberate trade, not the fix.
Node.js: FATAL ERROR: Reached heap limit — JavaScript heap out of memory (exit 134)
The V8 heap has its own ceiling, derived from system memory and the Node release, and it is often far below the RAM you have; when a build or server reaches it, V8 aborts with FATAL ERROR: Reached heap limit and exit code 134. Read the real limit with v8.getHeapStatistics().heap_size_limit, then raise it with --max-old-space-size (in MiB) or NODE_OPTIONS for a large workload, size it below the cgroup limit inside containers, and use --heapsnapshot-near-heap-limit to catch a leak in a long-running process. Exit 137 with no FATAL ERROR line is a container kill, not this.
Docker: "exec format error" when the container starts — wrong-platform image, no emulator, or a script with no shebang
The container exits on its first instruction with exec format error — the kernel's ENOEXEC, meaning the file exists but cannot be executed here. In practice that is an image built on one CPU architecture (an Apple-silicon Mac produces linux/arm64) and run on another (an x86_64 server) with no QEMU handler registered in binfmt_misc, or an entrypoint script whose first line is not a shebang. uname -m, docker image inspect and ls /proc/sys/fs/binfmt_misc tell the causes apart; the fix is an explicit docker buildx build --platform (or a manifest list for both), QEMU registration or --platform when you mean to emulate, and a #!/bin/sh line for the script.