How to fix fatal: ref HEAD is not a symbolic ref, cannot determine current branch in Git

GitINTERMEDIATEMEDIUMUpdated Dec 6, 2025· 223 views

This error occurs when Git commands attempt to determine the current branch name but the repository is in a detached HEAD state. This commonly happens in CI/CD pipelines where Git checkouts default to specific commits rather than branch references. The fix involves explicitly checking out a branch or using Git commands that handle detached HEAD gracefully.

Quick fix

Fixes fatal: ref HEAD is not a symbolic ref, cannot determine current branch

# Check HEAD status
git symbolic-ref HEAD
# If detached, you'll see: fatal: ref HEAD is not a symbolic ref

# Check what HEAD points to
cat .git/HEAD
# Detached: Shows a commit SHA (e.g., "a1b2c3d4e5f6...")
# Normal: Shows "ref: refs/heads/main" or similar

# Alternative check using rev-parse
git rev-parse --abbrev-ref HEAD
# Returns "HEAD" if detached, or branch name if on a branch

# Modern alternative (Git 2.22+)
git branch --show-current
# Returns empty string if detached, or branch name if on a branch
  1. Verify you're in detached HEAD state
  2. Checkout a local branch explicitly
  3. Fix for GitHub Actions
  4. Fix for Jenkins
git — error
# Check HEAD status
git symbolic-ref HEAD
# If detached, you'll see: fatal: ref HEAD is not a symbolic ref
# Check what HEAD points to
cat .git/HEAD
# Detached: Shows a commit SHA (e.g., "a1b2c3d4e5f6...")
# Normal: Shows "ref: refs/heads/main" or similar
# Alternative check using rev-parse
git rev-parse --abbrev-ref HEAD
# Returns "HEAD" if detached, or branch name if on a branch
# Modern alternative (Git 2.22+)
git branch --show-current
# Returns empty string if detached, or branch name if on a branch
fatal: ref HEAD is not a symbolic ref, cannot determine current branch

What this error means

The "fatal: ref HEAD is not a symbolic ref" error indicates that Git cannot determine which branch you're on because the repository is in a "detached HEAD" state. In this state, HEAD points directly to a specific commit SHA rather than pointing to a branch reference. Normally, HEAD is a "symbolic reference"—it contains the path to a branch (like `refs/heads/main`), and that branch contains the commit SHA. When you run `git symbolic-ref HEAD`, Git reads this reference and tells you the branch name. But when HEAD contains a raw commit SHA instead (detached HEAD), Git cannot determine the branch name and throws this error. This situation is extremely common in CI/CD environments: - **Jenkins** checks out commits by SHA by default, not by branch name - **GitHub Actions** defaults to checkout by commit for reproducibility - **GitLab CI** uses detached HEAD for most pipeline triggers - **Release events** (tags) trigger workflows on a commit, not a branch - **Maven Release Plugin** and similar tools call `git symbolic-ref` internally and fail The detached HEAD state isn't inherently problematic—Git works fine in this state for most operations. The error only surfaces when a command (or a tool wrapping Git) specifically needs to know the current branch name and uses `git symbolic-ref` to find it.

Typical symptoms

  • Maven release plugin fails with 'Detecting the current branch failed'
  • CI/CD pipeline fails during automated commits or tagging
  • GitHub Actions workflow fails with 'fatal: ref HEAD is not a symbolic ref'
  • Jenkins build fails during release or version bump operations
  • git symbolic-ref HEAD returns an error instead of a branch name
  • Automated PR creation tools fail to determine source branch
  • Release-it, semantic-release, or sbt-release plugins fail in CI
  • Commands that need branch name fail though other Git operations work fine

Common causes

  1. CI/CD system checked out a specific commit SHA instead of a branch reference
  2. GitHub Actions checkout action defaulting to commit checkout for pull requests
  3. Jenkins Git plugin using detached HEAD mode by default
  4. GitLab CI running in detached HEAD state for pipeline triggers
  5. Workflow triggered by a tag or release event (not a branch push)
  6. Checking out a specific commit with git checkout <sha>
  7. Build tools using git symbolic-ref internally to detect branch (Maven Release Plugin, etc.)
  8. Shallow clone missing branch reference information

How to fix "fatal: ref HEAD is not a symbolic ref, cannot determine current branch"

1Verify you're in detached HEAD state

First, confirm that your repository is actually in a detached HEAD state:

bash
# Check HEAD status
git symbolic-ref HEAD
# If detached, you'll see: fatal: ref HEAD is not a symbolic ref

# Check what HEAD points to
cat .git/HEAD
# Detached: Shows a commit SHA (e.g., "a1b2c3d4e5f6...")
# Normal: Shows "ref: refs/heads/main" or similar

# Alternative check using rev-parse
git rev-parse --abbrev-ref HEAD
# Returns "HEAD" if detached, or branch name if on a branch

# Modern alternative (Git 2.22+)
git branch --show-current
# Returns empty string if detached, or branch name if on a branch

Understanding your current state helps you choose the right fix.

2Checkout a local branch explicitly

The most direct fix is to checkout an actual branch before running commands that need branch detection:

bash
# Checkout the main branch
git checkout main

# Or checkout a specific branch
git checkout develop

# Force checkout if there are conflicts
git checkout -f main

Important considerations:
- Make sure the branch name matches your repository's default branch
- If the branch doesn't exist locally, fetch first: git fetch origin main:main
- In CI, the branch name is usually available as an environment variable

Quick pre-flight script for CI:

bash
#!/bin/bash
# Ensure we're on a branch, not detached HEAD
BRANCH=${BRANCH_NAME:-main}
git checkout -f "$BRANCH" || git checkout -B "$BRANCH" origin/"$BRANCH"
3Fix for GitHub Actions

In GitHub Actions, update your checkout step to reference a branch explicitly:

Basic fix - specify ref:

yaml
- uses: actions/checkout@v4
  with:
    ref: main

For pull requests - checkout the PR branch:

yaml
- uses: actions/checkout@v4
  with:
    ref: ${{ github.head_ref }}

For release events - use the release branch:

yaml
- uses: actions/checkout@v4
  with:
    ref: ${{ github.event.release.target_commitish }}

Dynamic branch checkout for any trigger:

yaml
- uses: actions/checkout@v4

- name: Checkout branch (not detached HEAD)
  run: |
    git checkout -B "${{ github.ref_name }}" || true

For create-pull-request action (v3.2.0+):

yaml
- uses: peter-evans/create-pull-request@v5
  with:
    base: main  # Handles commit checkouts by rebasing
4Fix for Jenkins

Jenkins Git plugin defaults to detached HEAD. Here's how to fix it:

Option 1: Configure via UI
1. Go to your Jenkins job configuration
2. Under "Source Code Management" → Git → "Additional Behaviours"
3. Click "Add" and select "Check out to matching local branch"
4. Leave the branch field as ** to match any branch automatically

Option 2: Pipeline script

groovy
checkout([
    $class: 'GitSCM',
    branches: [[name: '*/main']],
    extensions: [
        [$class: 'LocalBranch', localBranch: '**'],
        [$class: 'CleanCheckout']
    ],
    userRemoteConfigs: [[url: 'https://github.com/your/repo.git']]
])

Option 3: Declarative pipeline

groovy
pipeline {
    agent any
    stages {
        stage('Checkout') {
            steps {
                checkout scm: [
                    $class: 'GitSCM',
                    extensions: [[$class: 'LocalBranch', localBranch: '**']]
                ]
            }
        }
    }
}

Option 4: Post-checkout script

groovy
sh '''
    git checkout -f ${BRANCH_NAME}
'''
5Fix for GitLab CI

GitLab CI also uses detached HEAD by default. Add these steps to your pipeline:

In .gitlab-ci.yml:

yaml
before_script:
  - git checkout -B "$CI_COMMIT_REF_NAME" "$CI_COMMIT_SHA"
  # Or simply:
  - git checkout "$CI_COMMIT_REF_NAME"

For specific jobs:

yaml
release:
  stage: deploy
  before_script:
    - git fetch origin
    - git checkout -f $CI_COMMIT_REF_NAME
  script:
    - mvn release:prepare release:perform

Using Git strategy variable:

yaml
variables:
  GIT_STRATEGY: fetch
  GIT_CHECKOUT: "true"

before_script:
  - git checkout $CI_COMMIT_REF_NAME

GitLab CI environment variables for reference:
- CI_COMMIT_REF_NAME: Branch or tag name
- CI_COMMIT_SHA: Commit SHA
- CI_COMMIT_BRANCH: Branch name (empty for tags)
- CI_COMMIT_TAG: Tag name (empty for branches)

6Fix for Maven Release Plugin

The Maven Release Plugin uses git symbolic-ref internally to detect the branch. Fix the CI checkout first, or configure Maven:

Option 1: Fix CI checkout (recommended)
Use the Jenkins or GitLab solutions from previous steps to ensure you're on a branch before Maven runs.

Option 2: Specify branch in Maven command

bash
mvn release:prepare -DpushChanges=false -DlocalCheckout=true \
    -DdevelopmentVersion=1.0.1-SNAPSHOT \
    -DreleaseVersion=1.0.0

Option 3: Pre-release checkout script

bash
#!/bin/bash
# Run before mvn release:prepare
BRANCH=${GIT_BRANCH:-main}
BRANCH=${BRANCH#origin/}  # Remove origin/ prefix if present
git checkout -f "$BRANCH"

Option 4: Configure in pom.xml (limited)

xml
<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-release-plugin</artifactId>
    <version>3.0.1</version>
    <configuration>
        <localCheckout>true</localCheckout>
        <pushChanges>false</pushChanges>
    </configuration>
</plugin>

Note: The localCheckout option helps but doesn't fully resolve the branch detection issue.

7Use Git commands that handle detached HEAD gracefully

If you're writing scripts that need the branch name, use commands that handle detached HEAD without errors:

Instead of git symbolic-ref (errors on detached HEAD):

bash
# DON'T use this - fails on detached HEAD
git symbolic-ref --short HEAD

Use git branch --show-current (Git 2.22+):

bash
# Returns empty string on detached HEAD (no error)
BRANCH=$(git branch --show-current)
if [ -z "$BRANCH" ]; then
    echo "Detached HEAD - no branch"
else
    echo "On branch: $BRANCH"
fi

Use git rev-parse --abbrev-ref:

bash
# Returns "HEAD" if detached, otherwise returns branch name
BRANCH=$(git rev-parse --abbrev-ref HEAD)
if [ "$BRANCH" = "HEAD" ]; then
    echo "Detached HEAD state"
else
    echo "On branch: $BRANCH"
fi

Use symbolic-ref with quiet mode:

bash
# Exit code 1 on detached HEAD, no error message
if git symbolic-ref -q HEAD > /dev/null; then
    BRANCH=$(git symbolic-ref --short HEAD)
    echo "On branch: $BRANCH"
else
    echo "Detached HEAD state"
fi
8Handle shallow clones

Shallow clones in CI can exacerbate this issue by lacking full branch information:

GitHub Actions - full clone:

yaml
- uses: actions/checkout@v4
  with:
    fetch-depth: 0  # Full history
    ref: main       # Checkout branch, not detached

GitLab CI - disable shallow clone:

yaml
variables:
  GIT_DEPTH: 0  # Full clone, not shallow
  GIT_STRATEGY: clone

before_script:
  - git checkout $CI_COMMIT_REF_NAME

Jenkins - disable shallow clone:
In your Git configuration, under "Additional Behaviours":
1. Add "Advanced clone behaviours"
2. Uncheck "Shallow clone" or set depth to 0

Bamboo:
1. Go to Plan Configuration → Repositories → Git
2. Under "Advanced options", uncheck "Use shallow clones"
3. Enable "Force Clean Build" in Source Checkout task

General CI fetch command:

bash
# Convert shallow to full clone if needed
git fetch --unshallow origin || true
git fetch origin "+refs/heads/*:refs/remotes/origin/*"
git checkout $BRANCH_NAME

Advanced notes

Understanding HEAD and Symbolic References:

In Git, HEAD indicates "where you are now." There are two ways HEAD can work:

1. Symbolic reference (normal): HEAD contains ref: refs/heads/main, which points to the branch file, which contains the commit SHA
2. Detached HEAD: HEAD contains the commit SHA directly (e.g., a1b2c3d4...)

When tools run git symbolic-ref HEAD, they're asking Git to read HEAD as a symbolic reference. If HEAD contains a raw SHA, this fails with "fatal: ref HEAD is not a symbolic ref."

Why CI Systems Use Detached HEAD:

CI systems default to detached HEAD for good reasons:
- Reproducibility: Checking out by SHA ensures the exact same code runs
- Parallelism: Multiple builds can run simultaneously without branch conflicts
- Pull request handling: PRs have merge commits that don't exist on any branch
- Tag builds: Tags point to commits, not branches

The Modern Solution - git branch --show-current:

Git 2.22 (June 2019) introduced git branch --show-current, which:
- Returns the current branch name if on a branch
- Returns empty string (not an error) if in detached HEAD
- Is safer for scripting than git symbolic-ref

If you maintain build scripts, consider switching to this command.

Workaround When You Can't Modify CI Config:

If you can't change the CI configuration but need branch detection, use this fallback chain:

bash
get_branch() {
    # Try symbolic-ref first (fastest)
    git symbolic-ref --short HEAD 2>/dev/null && return

    # Fall back to CI environment variables
    [ -n "$GITHUB_HEAD_REF" ] && echo "$GITHUB_HEAD_REF" && return
    [ -n "$GITHUB_REF_NAME" ] && echo "$GITHUB_REF_NAME" && return
    [ -n "$CI_COMMIT_REF_NAME" ] && echo "$CI_COMMIT_REF_NAME" && return
    [ -n "$GIT_BRANCH" ] && echo "${GIT_BRANCH#origin/}" && return
    [ -n "$BRANCH_NAME" ] && echo "$BRANCH_NAME" && return

    # Last resort: try to find branches containing current commit
    git branch -r --contains HEAD 2>/dev/null | head -1 | sed 's/.*origin\///'
}

Platform-Specific Environment Variables:

| Platform | Branch Variable |
|----------|-----------------|
| GitHub Actions | GITHUB_REF_NAME, GITHUB_HEAD_REF (PRs) |
| GitLab CI | CI_COMMIT_REF_NAME, CI_COMMIT_BRANCH |
| Jenkins | BRANCH_NAME, GIT_BRANCH |
| CircleCI | CIRCLE_BRANCH |
| Travis CI | TRAVIS_BRANCH |
| Azure Pipelines | BUILD_SOURCEBRANCH |
| Bitbucket Pipelines | BITBUCKET_BRANCH |

When Detached HEAD Is Actually Correct:

Some situations legitimately require detached HEAD:
- Inspecting historical commits (git checkout abc123)
- Bisecting (git bisect)
- Running CI on exact commits for reproducibility
- Checking out tags for release builds

In these cases, instead of fighting the detached HEAD, update your tools/scripts to not require branch detection, or to fall back to the commit SHA when no branch is available.

Frequently asked questions

What does "fatal: ref HEAD is not a symbolic ref, cannot determine current branch" mean?
The "fatal: ref HEAD is not a symbolic ref" error indicates that Git cannot determine which branch you're on because the repository is in a "detached HEAD" state. In this state, HEAD points directly to a specific commit SHA rather than pointing to a branch reference. Normally, HEAD is a "symbolic reference"—it contains the path to a branch (like refs/heads/main), and that branch contains the commit SHA.
How do I fix "fatal: ref HEAD is not a symbolic ref, cannot determine current branch"?
This error occurs when Git commands attempt to determine the current branch name but the repository is in a detached HEAD state. This commonly happens in CI/CD pipelines where Git checkouts default to specific commits rather than branch references. The fix involves explicitly checking out a branch or using Git commands that handle detached HEAD gracefully.
What causes "fatal: ref HEAD is not a symbolic ref, cannot determine current branch"?
CI/CD system checked out a specific commit SHA instead of a branch reference GitHub Actions checkout action defaulting to commit checkout for pull requests Jenkins Git plugin using detached HEAD mode by default
Is the "fatal: ref HEAD is not a symbolic ref, cannot determine current branch" error serious?
It is a medium-severity error — more of a blocker than a risk. The steps above resolve it.

Official resources & further reading