Git

Git Submodule Detached HEAD State After Checkout, How to Track Main Branch

· by DebuggedIt

Quick answer

A submodule ending up in detached HEAD state after checkout is actually the normal, default Git behavior for submodules, not an error — Git submodules are...

A submodule ending up in detached HEAD state after checkout is actually the normal, default Git behavior for submodules, not an error — Git submodules are designed to pin to a specific, exact commit by default (rather than following a branch), and understanding this default is the first step to configuring branch-tracking behavior if that's what you actually want instead.

The Problem

After cloning a repository with submodules, or running git submodule update, checking the submodule's status shows it's not on any branch:

cd path/to/submodule
git status
HEAD detached at a1b2c3d

Making commits within the submodule while in this state, or expecting it to automatically reflect the latest changes from its own remote's main branch, doesn't behave as you might expect from a normal, non-submodule Git repository.

Why It Happens

Git submodules are specifically designed to pin the parent repository to an exact, specific commit of the submodule — not to a moving branch reference — because the entire purpose of a submodule is typically to have a reliable, reproducible reference to a known, specific version of the dependency, rather than automatically tracking whatever the latest commit on some branch happens to be at any given moment. When you run git submodule update, Git checks out that exact pinned commit within the submodule directory, which necessarily leaves it in detached HEAD state, since a specific commit (rather than a branch name) is what's actually being checked out.

The Fix

This detached HEAD state is expected and correct if you're just consuming the submodule as a fixed dependency — you don't need to "fix" it for normal usage, where the parent repository controls exactly which commit of the submodule is used, and updates to that pinned commit happen deliberately, not automatically.

If you specifically want the submodule to track and follow a branch (staying updated with the latest commits on that branch, rather than remaining pinned to one fixed commit), explicitly configure this behavior:

git submodule set-branch --branch main path/to/submodule

Then update the submodule to actually check out and follow that branch, rather than a detached specific commit:

git submodule update --remote path/to/submodule

The --remote flag specifically tells Git to update the submodule to the latest commit on its configured tracking branch, rather than to whatever commit is currently pinned in the parent repository's own record — this is the mechanism that actually pulls in newer commits, rather than the plain git submodule update (without --remote), which only checks out the exact commit the parent repository currently has recorded.

If you need to make changes directly within the submodule (not just consume it, but actively develop and commit to it), explicitly check out a proper branch first, rather than committing while in detached HEAD state, since commits made while detached aren't attached to any branch and can be easily lost if you switch away without creating a branch to preserve them:

cd path/to/submodule
git checkout main  # or your intended working branch
# now make and commit your changes normally, properly on a branch
git push origin main

After making and pushing changes within the submodule itself, remember to also commit the updated submodule reference within the parent repository — the parent repository needs its own separate commit recording the new commit hash the submodule should point to, since simply updating the submodule's own repository doesn't automatically update what the parent repository has pinned:

cd ..  # back to parent repository root
git add path/to/submodule
git commit -m "Update submodule to latest commit"
git push

Still Not Working?

If you consistently want every clone of the parent repository to automatically track a submodule's branch rather than remaining pinned to a specific commit, configure this behavior persistently in the parent repository's .gitmodules file, so it applies automatically for every future clone or fresh checkout, rather than needing manual reconfiguration each time:

# .gitmodules
[submodule "path/to/submodule"]
    path = path/to/submodule
    url = https://github.com/example/submodule-repo.git
    branch = main

With branch = main configured here, running git submodule update --remote consistently updates to the latest commit on main for anyone working with this repository, rather than each person needing to individually run set-branch themselves after every fresh clone.