Git

Git Submodule Not Updating After Pull

A submodule staying on an old commit after pulling the parent repository is expected Git behavior, not a bug — a normal git pull updates the parent repository's own tracked reference to which submodule commit it expects, but doesn't automatically update the actual submodule's working directory to match.

The Problem

After pulling changes to the parent repository, code inside a submodule still reflects an older version, even though you can see (through git log or checking the remote) that the submodule itself has new commits that should be included.

Why It Happens

Git submodules work by having the parent repository track a specific commit hash for each submodule, rather than tracking a branch continuously. A plain git pull on the parent repository updates what commit hash the parent repository expects the submodule to be at, but doesn't automatically check out that commit inside the submodule's own directory — that requires a separate, explicit step. Common causes of confusion:

  • Not knowing that submodule updates require this separate command at all, assuming a normal pull handles everything recursively by default
  • Running git pull without the recursive flag, which is required to also pull and check out the correct commit inside each submodule automatically
  • The submodule was updated in the parent repository's tracked reference, but no one has actually pushed the corresponding new commit to the submodule's own remote repository yet, so there's nothing new to pull into the submodule even with the correct commands

The Fix

After a normal pull, explicitly update submodules to match what the parent repository now expects:

git submodule update --init --recursive

This checks out each submodule to the exact commit the parent repository's tracked reference points to, initializing any submodules that weren't previously set up, and recursing into nested submodules if any exist.

To avoid needing to remember this as a separate step every time, configure Git to automatically update submodules whenever you pull the parent repository:

git config submodule.recurse true

With this setting enabled, a normal git pull automatically includes the equivalent of the submodule update step, without needing to run it manually afterward each time.

Alternatively, without changing global configuration, you can pull and update submodules in a single explicit command whenever needed:

git pull --recurse-submodules

If the submodule appears to have no new commits available even after running the update command, confirm the submodule's own remote actually has the commit the parent repository expects, since it's possible for the parent's tracked reference to point at a commit that was never actually pushed to the submodule's remote:

cd path/to/submodule
git fetch
git log --oneline -5

Compare the commit hash the parent repository expects (visible via git submodule status from the parent repo) against what's actually available in the submodule's remote history.

Still Not Working?

If the submodule directory appears empty or shows as uninitialized rather than just outdated, it likely was never initialized in your local clone at all — common when cloning a repository with submodules without the recursive flag from the start:

git clone --recurse-submodules <repository-url>

If you already have a clone made without this flag, run the initialization step explicitly rather than re-cloning from scratch:

git submodule update --init --recursive

This same command handles both the "never initialized" and "outdated" cases, making it the safe default to run whenever submodule state seems inconsistent with what you expect, regardless of the specific underlying cause.

It's also worth checking whether the submodule is in a detached HEAD state after updating, which is actually the normal, expected state for a submodule checked out to a specific commit — this can look alarming if you're used to submodules behaving like regular branches, but it's simply how Git tracks a submodule at an exact commit rather than a moving branch reference:

cd path/to/submodule
git status
# HEAD detached at a1b2c3d - this is expected for submodules

If you need to make changes inside the submodule itself (rather than just consuming its current pinned commit), you'll want to explicitly check out a branch inside it first, since committing directly while in detached HEAD state can leave those commits difficult to find later if you switch away without creating a branch to reference them.

Finally, if submodule updates consistently cause friction for your team, consider documenting the required commands directly in your project's README or setup script, since forgetting the extra recursive step after a pull is a very common source of "my code doesn't match what CI is running" confusion, particularly for anyone newer to the project who isn't yet in the habit of running the update command automatically.