---
# === IDENTITY ===
id: software/debugging/git-detached-head/2026
canonical_question: "How do I recover from a detached HEAD state in Git?"
aliases:
  - "git detached HEAD"
  - "HEAD detached at commit"
  - "git checkout commit hash detached"
  - "git detached HEAD save work"
  - "git recover lost commits detached HEAD"
  - "git detached HEAD branch"
  - "git HEAD detached tag"
  - "git orphaned commits reflog"
  - "detached HEAD how to get back to branch"
  - "git HEAD not on branch"
entity_type: software_reference
domain: software > debugging > git_detached_head
region: global
jurisdiction: global
temporal_scope: 2005-2026

# === VERIFICATION ===
last_verified: 2026-02-23
confidence: 0.95
freshness: yearly
version: 2.0
first_published: 2026-02-19

# === TEMPORAL VALIDITY ===
temporal_validity:
  status: stable
  last_breaking_change: "Git 2.23 introduced git switch (2019-08)"
  next_review: 2026-08-22
  change_sensitivity: low

# === CONSTRAINTS ===
constraints:
  - "Never switch away from a detached HEAD without first saving new commits to a branch — orphaned commits are garbage-collected after ~30 days"
  - "git switch requires Git 2.23+ — on older systems (RHEL 7, CentOS 7) use git checkout -b instead"
  - "Do not try to 'fix' detached HEAD in CI/CD pipelines — SHA checkout is the expected correct behavior for reproducibility"
  - "Never run git gc or git prune while trying to recover orphaned commits — this may permanently destroy them"
  - "Shallow clones (--depth=1) have limited reflog history — orphaned commits in shallow clones may be unrecoverable"

# === SKIP CONDITIONS ===
skip_this_unit_if:
  - condition: "User has git merge conflicts, not detached HEAD"
    use_instead: "software/debugging/git-merge-conflicts/2026"
  - condition: "User wants to undo a git rebase or git reset, not recover from detached HEAD"
    use_instead: "software/debugging/git-undo-rebase-reset/2026"
  - condition: "User is troubleshooting SSH or HTTPS authentication errors with git"
    use_instead: "software/debugging/git-authentication-errors/2026"

# === AGENT HINTS ===
inputs_needed:
  - key: new_commits_made
    question: "Did you make any new commits while in the detached HEAD state?"
    type: choice
    options: ["Yes — I need to save them", "No — I just want to get back to my branch", "Not sure"]
  - key: already_switched_away
    question: "Have you already switched away from the detached HEAD (losing access to the commits)?"
    type: choice
    options: ["No — I am still in detached HEAD", "Yes — I already switched and lost the commits"]
  - key: git_version
    question: "What version of Git are you using?"
    type: choice
    options: ["Git 2.23+ (has git switch)", "Older Git (no git switch)", "Not sure"]

# === DISTRIBUTION ===
canonical_source: "https://knowledgelib.io/software/debugging/git-detached-head/2026"
suggested_citation: "Source: knowledgelib.io — AI Knowledge Library (verified 2026-02-23)"

# === RELATED UNITS ===
related_kos:
  related_to:
    - id: "software/debugging/git-merge-conflicts/2026"
      label: "Git Merge Conflicts"
    - id: "software/debugging/ssl-tls-certificate-errors/2026"
      label: "SSL/TLS Certificate Errors"
  solves:
    - id: "software/debugging/git-lost-commits/2026"
      label: "Recovering Lost Git Commits"
  often_confused_with:
    - id: "software/debugging/git-undo-rebase-reset/2026"
      label: "Undoing Git Rebase or Reset"

# === SOURCES (8 authoritative sources) ===
sources:
  - id: src1
    title: "Git Documentation — git-checkout"
    author: Git Project
    url: https://git-scm.com/docs/git-checkout
    type: official_docs
    published: 2025-01-01
    reliability: authoritative
  - id: src2
    title: "Git Documentation — git-reflog"
    author: Git Project
    url: https://git-scm.com/docs/git-reflog
    type: official_docs
    published: 2025-01-01
    reliability: authoritative
  - id: src3
    title: "Graphite — Detached HEAD State"
    author: Graphite
    url: https://graphite.dev/guides/git-detached-head
    type: technical_blog
    published: 2024-08-01
    reliability: high
  - id: src4
    title: "Baeldung — Git Detached HEAD"
    author: Baeldung
    url: https://www.baeldung.com/git/detached-head
    type: technical_blog
    published: 2024-08-01
    reliability: high
  - id: src5
    title: "CircleCI — Understanding Detached HEAD"
    author: CircleCI
    url: https://circleci.com/blog/git-detached-head-state/
    type: technical_blog
    published: 2024-06-01
    reliability: high
  - id: src6
    title: "GeeksForGeeks — Git Detached HEAD"
    author: GeeksForGeeks
    url: https://www.geeksforgeeks.org/git-detached-head/
    type: community_resource
    published: 2024-06-01
    reliability: high
  - id: src7
    title: "Stack Overflow — Recover from git detached head"
    author: Stack Overflow Community
    url: https://stackoverflow.com/questions/10228760/how-do-i-fix-a-git-detached-head
    type: community_resource
    published: 2024-06-01
    reliability: high
  - id: src8
    title: "Pro Git Book — Git Internals — Git References"
    author: Scott Chacon, Ben Straub
    url: https://git-scm.com/book/en/v2/Git-Internals-Git-References
    type: official_docs
    published: 2024-01-01
    reliability: authoritative
---

# How Do I Recover from a Detached HEAD State in Git?

## TL;DR

- **Bottom line**: Detached HEAD means `HEAD` points directly to a commit instead of a branch name. It is not an error — it is intentional for inspecting history. The danger: new commits made in this state are not on any branch and may be garbage-collected. Fix depends on intent: if you haven't committed anything new, just `git switch <branch>`. If you have new commits you want to keep, create a branch first: `git switch -c <new-branch>`.
- **Key tool/command**: `git switch -c <new-branch>` — saves all detached-HEAD commits to a new branch without losing them.
- **Watch out for**: Switching away from a detached HEAD **without creating a branch first** causes all commits made in the detached state to become unreachable (orphaned). You have a ~30-day window to recover them via `git reflog` before garbage collection.
- **Works with**: Git 2.23+ (`git switch`). For older Git, use `git checkout -b <branch>`. Latest stable: Git 2.53 (Feb 2026).

## Constraints
<!-- Agents: read this section before recommending any action from this unit.
     These are hard constraints that must not be violated. -->

- **Never switch away without saving**: If you made commits in detached HEAD, always run `git switch -c <branch>` before switching to another branch. Orphaned commits are garbage-collected after ~30 days and become permanently unrecoverable. [src1, src3]
- **Git 2.23+ required for git switch**: On older systems (RHEL 7, CentOS 7, some enterprise Linux distributions), `git switch` is not available. Use `git checkout -b` as the equivalent fallback. [src1]
- **Do not "fix" detached HEAD in CI/CD**: Pipelines (GitHub Actions, Jenkins, GitLab CI) check out a specific SHA for reproducibility. Detached HEAD is the expected and correct behavior. Do not create branches or attempt recovery. [src5]
- **Never run git gc or git prune during recovery**: These commands may permanently destroy orphaned commits you are trying to recover. Complete recovery first, verify via `git log`, then allow GC to proceed normally. [src2, src8]
- **Shallow clones limit recovery**: CI/CD systems using `--depth=1` have very limited reflog history. Orphaned commits in shallow clones may be unrecoverable via reflog — use `git fsck --lost-found` as a last resort. [src5]

## Quick Reference

| # | Goal | Command | Notes |
|---|---|---|---|
| 1 | Check if you're in detached HEAD | `git status` | Shows "HEAD detached at `<hash>`" [src1] |
| 2 | See current HEAD position | `git log --oneline -5` | Top commit = where HEAD is [src1] |
| 3 | Save new commits to new branch | `git switch -c <new-branch>` | Best recovery path; Git 2.23+ [src1, src3] |
| 4 | Save new commits to new branch (old Git) | `git checkout -b <new-branch>` | Equivalent for Git < 2.23 [src1] |
| 5 | Go back to branch (no new commits) | `git switch <branch>` or `git checkout <branch>` | Discards detached state cleanly [src1] |
| 6 | Cherry-pick detached commits to existing branch | `git cherry-pick <hash>` | Copies specific commits to current branch [src1] |
| 7 | Find lost commits after switching away | `git reflog` | Shows all HEAD positions for ~30 days [src2] |
| 8 | Recover lost commit by hash | `git branch recover-work <hash>` | Create branch at recovered commit [src1, src2] |
| 9 | Intentionally inspect a historical commit | `git switch --detach <hash>` | Safe read-only view of history [src1, src5] |
| 10 | Check out a tag (enters detached HEAD) | `git checkout v1.2.3` | Tags are immutable; always detaches [src1, src6] |
| 11 | Check out remote branch (enters detached HEAD) | `git checkout origin/main` | Creates detached state; use `git switch main` instead [src1] |
| 12 | Create local tracking branch from remote | `git switch -c main origin/main` | Fixes "accidentally detached on origin/main" [src1, src3] |
| 13 | Clone at specific revision (intentional detach) | `git clone --revision <ref> <url>` | Git 2.49+; no tracking branch, HEAD detached [src1] |

## Decision Tree

```
START -- "HEAD detached at <hash>" in git status
|-- Did you make any NEW commits in the detached state?
|   |-- NO -> Just switch back to your branch:
|   |         git switch main (or git checkout main) [src1]
|   |         Done. Nothing is lost.
|   +-- YES -> Save those commits first:
|             git switch -c my-new-feature   [preferred, Git 2.23+]
|             git checkout -b my-new-feature [Git < 2.23]
|             Then merge/rebase into your target branch as needed.
|
|-- Already switched away and lost the commits?
|   |-- Find them: git reflog (look for the detached HEAD entries)
|   +-- Recover:  git branch recover-work <commit-hash>
|                 git switch recover-work    [src2]
|
|-- WHY did you end up detached?
|   |-- git checkout <commit-hash>   -> inspecting history (expected)
|   |-- git checkout <tag>           -> tags always detach [src6]
|   |-- git checkout origin/main     -> use git switch main instead [src3]
|   |-- git clone --revision <ref>   -> intentional minimal clone, Git 2.49+ [src1]
|   +-- CI/CD pipeline              -> clone is shallow/checkout by SHA (expected) [src5]
|
+-- Want to avoid accidental detached HEAD?
    +-- Prefer git switch over git checkout for branch navigation [src1]
```

## Step-by-Step Guide

### 1. Understand what detached HEAD means

Normally, `HEAD` -> branch pointer -> commit. In detached HEAD, `HEAD` -> commit directly. [src1, src8]

```bash
# Normal state
cat .git/HEAD          # -> ref: refs/heads/main
git branch             # -> * main

# Detached HEAD state
cat .git/HEAD          # -> a1b2c3d4e5f6... (a commit SHA)
git status             # -> HEAD detached at a1b2c3d
git branch             # -> * (HEAD detached at a1b2c3d)
```

The key insight: commits made in detached HEAD are **not on any branch**. If you switch branches, those commits become unreachable and are eventually garbage-collected. [src1, src3, src5]

**Verify**: `git status` -> expected output: `HEAD detached at <hash>` or `On branch <name>`

### 2. Recover: save new commits to a new branch

The most common recovery path. [src1, src3, src7]

```bash
# Step 1: Confirm you're in detached HEAD and see what you've done
git status
git log --oneline -10   # see commits made in detached state

# Step 2: Create a new branch at the current position -- saves all commits
git switch -c my-feature-branch      # Git 2.23+
# OR
git checkout -b my-feature-branch    # older Git

# Step 3: Verify
git status              # now shows "On branch my-feature-branch"
git log --oneline -5    # all commits preserved

# Step 4: Push (if needed)
git push -u origin my-feature-branch
```

**Verify**: `git status` -> expected output: `On branch my-feature-branch`

### 3. Recover: save commits to an existing branch

If you want to integrate the detached-HEAD work into an existing branch: [src1, src7]

```bash
# Note the commit hash(es) from detached state
git log --oneline

# Switch to target branch
git switch main     # or git checkout main

# Option A: cherry-pick specific commit(s)
git cherry-pick a1b2c3d   # copy one commit
git cherry-pick a1b2c3d..f4e5d6c   # range of commits

# Option B: merge the detached work (create temp branch first)
git switch -c temp-work a1b2c3d   # branch at last detached commit
git switch main
git merge temp-work
git branch -d temp-work   # clean up
```

**Verify**: `git log --oneline -5` -> expected output: cherry-picked/merged commits visible on target branch

### 4. Discard: return to branch without saving

If you made no commits (or want to throw them away): [src1]

```bash
# Just switch back -- Git warns if uncommitted changes exist
git switch main
# OR
git checkout main

# If you have uncommitted changes to discard:
git checkout -f main   # force -- discards all uncommitted changes
```

**Verify**: `git status` -> expected output: `On branch main`

### 5. Recover lost commits via reflog

If you already switched away and lost the commits: [src2, src7]

```bash
# git reflog shows every HEAD position for ~30 days
git reflog

# Example output:
# a1b2c3d HEAD@{0}: checkout: moving from abc123 to main
# f4e5d6c HEAD@{1}: commit: add important feature  <- this is what you lost
# 7g8h9i0 HEAD@{2}: commit: work in progress
# ...

# Create a branch at the lost commit
git branch recover-work f4e5d6c

# Or cherry-pick it directly
git cherry-pick f4e5d6c
```

**Verify**: `git branch -v` -> expected output: `recover-work f4e5d6c add important feature`

### 6. How detached HEAD happens in CI/CD

CI/CD pipelines often check out a specific SHA for reproducibility. This always creates detached HEAD — it is the expected correct behavior. [src5]

```yaml
# GitHub Actions: by default checks out detached HEAD
- uses: actions/checkout@v4
  # HEAD is detached at the commit SHA -- expected behavior

# To get a real branch reference:
- uses: actions/checkout@v4
  with:
    ref: ${{ github.head_ref }}   # for PRs
```

Git 2.49+ also introduced `git clone --revision <ref>` which intentionally creates a detached HEAD clone for minimal CI builds. [src1]

```bash
# Minimal CI clone at specific tag -- intentional detached HEAD
git clone --revision refs/tags/v1.0 --depth=1 https://github.com/org/repo.git
```

## Code Examples

### Bash: safe detached HEAD detector and recovery helper

> Full script: [bash-safe-detached-head-detector-and-recovery-help.sh](scripts/bash-safe-detached-head-detector-and-recovery-help.sh) (61 lines)

```bash
#!/bin/bash
# Input:  Git repository (optional branch name to recover to)
# Output: Detached HEAD status report + guided recovery
RECOVER_BRANCH="${1:-}"
echo "=== Git HEAD State Inspector ==="
# ... (see full script)
```

### Python: git repository HEAD state checker

> Full script: [python-git-repository-head-state-checker.py](scripts/python-git-repository-head-state-checker.py) (105 lines)

```python
#!/usr/bin/env python3
"""
Input:  path to a Git repository directory (default: current directory)
Output: dict with HEAD state, branch info, detached commits, and recovery commands
"""
# ... (see full script)
```

### Bash: reflog-based lost commit recovery

> Full script: [bash-reflog-based-lost-commit-recovery.sh](scripts/bash-reflog-based-lost-commit-recovery.sh) (53 lines)

```bash
#!/bin/bash
# Input:  optional keyword to filter reflog entries
# Output: list of potential lost commits with recovery commands
KEYWORD="${1:-}"
echo "=== Lost Commit Recovery via reflog ==="
# ... (see full script)
```

## Anti-Patterns

### Wrong: Committing new work in detached HEAD without creating a branch

```bash
# BAD -- making commits in detached state without securing them [src1, src3, src5]
git checkout a1b2c3d    # enters detached HEAD
# ... make changes ...
git add .
git commit -m "important feature"   # commit exists but is on NO branch
git checkout main                   # detached HEAD commits are now orphaned!
# Git warning: "Warning: you are leaving N commits behind, not connected to any branch"
# If ignored, these commits will be GC'd in ~30 days
```

### Correct: Create a branch before switching away

```bash
# GOOD -- lock in your work before switching [src1, src3]
git checkout a1b2c3d    # enters detached HEAD
# ... make changes ...
git add .
git commit -m "important feature"

# Save BEFORE switching away
git switch -c my-feature   # creates branch at current position
git switch main            # now safe -- my-feature branch preserves commits

# Or do it all at once (create branch from current detached position):
git switch -c my-feature && git push -u origin my-feature
```

### Wrong: Using git checkout for branch navigation (causes accidental detached HEAD)

```bash
# BAD -- git checkout is ambiguous; easy to accidentally detach [src1, src3]
git checkout main      # fine -- goes to branch
git checkout a1b2c3d   # detached HEAD! (same command, very different behavior)
git checkout v1.2.3    # detached HEAD! (checking out a tag)

# On a typo, old commit hash, or tag name -- silently detaches
```

### Correct: Use git switch for branch navigation (explicit syntax)

```bash
# GOOD -- git switch is unambiguous; fails clearly on wrong input [src1]
git switch main           # goes to branch (works)
git switch a1b2c3d        # ERROR: "fatal: invalid reference: a1b2c3d" (commits need --detach)
git switch --detach main  # explicitly enters detached HEAD -- intentional

# To inspect a historical commit deliberately:
git switch --detach a1b2c3d   # clear intent: you know you're detaching
```

### Wrong: Checking out origin/main directly (causes detached HEAD)

```bash
# BAD -- origin/main is a remote-tracking ref, not a local branch [src1, src3, src6]
git checkout origin/main   # detached HEAD at origin/main
# Any commits here are orphaned; you're not on 'main'
```

### Correct: Use git switch to track the remote branch

```bash
# GOOD -- creates a local tracking branch [src1, src3]
git switch main                          # if local 'main' exists, tracks origin/main
git switch -c main origin/main           # create local branch tracking remote
git checkout --track origin/main         # equivalent (older syntax)
```

## Common Pitfalls

- **Git's warning is easy to dismiss**: When you switch away from detached HEAD with uncommitted commits, Git prints a warning with the orphaned commit hashes. Many developers dismiss this warning. **Write down the hashes immediately** or create a branch right then. [src1, src3, src5]
- **Garbage collection has a ~30-day window**: Orphaned commits from detached HEAD are not immediately deleted. `git reflog` keeps them accessible for 30 days (configurable via `gc.reflogExpire`). After that, `git gc` deletes them permanently. [src2, src8]
- **Tags always cause detached HEAD**: Checking out a tag (`git checkout v1.0`) always enters detached HEAD because tags are immutable — they cannot have new commits. This is expected but surprises newcomers. [src1, src6]
- **CI/CD pipelines are always in detached HEAD**: GitHub Actions, Jenkins, and most CI systems check out a specific commit SHA for reproducibility. This is correct behavior; don't try to "fix" it inside CI. [src5]
- **`git checkout <branch>` vs `git switch <branch>`**: Both work for switching to a branch but `git checkout` is overloaded (it also checks out files, trees, and commits). `git switch` (Git 2.23+) is branch-only and gives clearer error messages for detached-HEAD scenarios. [src1, src3]
- **Detached HEAD with stashed changes**: A `git stash pop` in detached HEAD creates a commit in the detached state. Create a branch before applying stash work if you intend to keep it. [src1]
- **Interactive rebase uses detached HEAD internally**: During `git rebase -i`, Git temporarily enters detached HEAD to replay commits. Do not be alarmed by this; it resolves automatically when the rebase completes or is aborted. [src1]
- **Git 2.49+ `--revision` clone creates detached HEAD**: The new `git clone --revision` flag (Git 2.49, March 2025) intentionally creates a detached HEAD without a tracking branch. This is by design for CI/CD use cases. [src1]

## Diagnostic Commands

```bash
# === Detect detached HEAD ===
git status                 # shows "HEAD detached at <hash>" if detached
cat .git/HEAD              # shows SHA (detached) or "ref: refs/heads/<branch>" (normal)
git branch                 # shows "* (HEAD detached at <hash>)" if detached

# === Understand what happened ===
git log --oneline -10      # see recent commits
git reflog                 # full HEAD movement history
git log --oneline --graph --all  # all branches including detached commits

# === Recovery ===
git switch -c <new-branch>           # save AND create branch (Git 2.23+)
git checkout -b <new-branch>         # same (older Git)
git branch <name>                    # create branch without switching
git switch <existing-branch>         # discard detached state and go home
git cherry-pick <hash>               # copy specific commit to current branch

# === Lost commit recovery ===
git reflog                           # find the lost commit hash
git reflog --pretty=format:"%h %gs %ar"  # friendlier format
git branch recover <lost-hash>       # create branch at lost commit
git fsck --lost-found                # find ALL unreachable objects (last resort)

# === Prevention ===
git switch --detach <hash>           # explicit intentional detach
git switch <branch>                  # always use switch for branch navigation
git config --global advice.detachedHead true  # ensure warnings are on
```

## Version History & Compatibility

| Feature | Available Since | Notes |
|---|---|---|
| `git switch` | Git 2.23 (Aug 2019) | Preferred over `git checkout` for branch navigation [src1] |
| `git switch -c` | Git 2.23 | Create and switch (replaces `git checkout -b`) [src1] |
| `git switch --detach` | Git 2.23 | Explicit intentional detached HEAD [src1] |
| `git restore` | Git 2.23 | Replaces `git checkout -- <file>` for file restoration [src1] |
| `git clone --revision` | Git 2.49 (Mar 2025) | Intentional detached HEAD clone for CI/CD [src1] |
| `git reflog` | Git 1.5.0 | Always available; default 30-day window [src2] |
| `git fsck --lost-found` | Git 1.5.0 | Find all unreachable objects [src2] |
| `gc.reflogExpire` | Git 1.6.0 | Configure how long reflog retains entries [src8] |
| Detached HEAD concept | Git 1.0 | Core Git concept; unchanged since inception [src8] |
| Latest stable | Git 2.53 (Feb 2026) | All detached HEAD features fully supported |

## When to Use / When Not to Use

| Use Detached HEAD | Don't Use Detached HEAD For | Use Instead |
|---|---|---|
| Inspecting historical code: `git switch --detach <hash>` | Starting new development work | `git switch -c <feature-branch>` |
| Building/testing a specific tagged release | Hotfixes — create a branch instead | `git switch -c hotfix/<issue>` |
| CI/CD pipelines checking out exact SHAs (expected) | Any work you intend to commit and keep | Any named branch |
| Bisecting: `git bisect` manages detached HEAD for you | Code review — use a named branch | `git switch -c review/<pr>` |
| Minimal CI clones: `git clone --revision` (Git 2.49+) | Long-running experiments | `git switch -c experiment/<name>` |

## Important Caveats

- **`git switch` may not be available on older systems**: Git 2.23 was released August 2019, but some enterprise Linux distributions (RHEL 7, CentOS 7) ship with much older Git. On those systems, use `git checkout -b` instead of `git switch -c`.
- **`git fsck --lost-found` can recover beyond reflog window**: If reflog has expired, `git fsck --lost-found` writes unreachable objects to `.git/lost-found/`. This is the last resort for very old orphaned commits.
- **Shallow clones limit reflog recovery**: CI/CD systems that use `--depth=1` shallow clones have very limited reflog history. Commits orphaned in a shallow clone may not be recoverable via reflog.
- **Interactive rebase uses detached HEAD internally**: During `git rebase -i`, Git temporarily enters detached HEAD to replay commits. Do not be alarmed by this; it resolves automatically when the rebase completes or is aborted.
- **`ORIG_HEAD` preserves pre-merge/rebase position**: After a merge, rebase, or reset, Git stores the previous `HEAD` in `ORIG_HEAD`. Use `git reset --hard ORIG_HEAD` to undo a bad merge or rebase quickly.
- **Git 2.49+ improved detached HEAD messages**: Starting with Git 2.49 (March 2025), Git provides clearer guidance messages when entering or leaving detached HEAD state, making recovery steps more obvious.

## Related Units
<!-- Generated from related_kos frontmatter -->

- [Git Merge Conflicts](/software/debugging/git-merge-conflicts/2026)
- [SSL/TLS Certificate Errors](/software/debugging/ssl-tls-certificate-errors/2026)
- [Recovering Lost Git Commits](/software/debugging/git-lost-commits/2026)
- [Undoing Git Rebase or Reset](/software/debugging/git-undo-rebase-reset/2026)
