Fix empty submodule folders after cloning
I cloned a repo and the submodule folders are empty — how do I fix it?
If you’re seeing this error
warning: unable to rmdir '<path>': Directory not emptySubmodule '<name>' (<url>) registered for path '<path>'You’re in the right place — the fix is below.
Short answer
Cloning records submodules but doesn't populate them. Run git submodule update --init --recursive, or clone with --recurse-submodules next time so it happens automatically.
git submodule update --init --recursive
Fetches and checks out every submodule, including nested ones
Does this match your situation?
- A directory listed in .gitmodules exists but is empty.
- The build fails with missing files from a dependency folder.
- git status reports submodules as 'not initialized'.
- A submodule shows as modified with '(new commits)' you didn't make.
Step-by-step fix
Initialise and fetch every submodule
This registers each submodule from .gitmodules and checks out the exact commit your repository pins. --recursive handles submodules that themselves contain submodules.
step 1git submodule update --init --recursive git submodule statusClone correctly next time
One flag does the whole thing at clone time and avoids the problem entirely.
step 2git clone --recurse-submodules https://github.com/user/repo.git # make it the default forever: git config --global submodule.recurse trueFix a submodule showing unexpected '(new commits)'
This means the submodule is checked out at a different commit than the parent records — usually because you built inside it, or updated it without committing the new pointer. Reset it back to the pinned commit.
step 3git submodule update --init --recursive --force # or intentionally record the new commit: git add path/to/submodule git commit -m "Update submodule to latest"Update a submodule to its latest upstream commit
Submodules are pinned deliberately, so moving one forward is an explicit action that produces a commit in the parent repository.
step 4git submodule update --remote path/to/submodule git add path/to/submodule git commit -m "Bump submodule to latest upstream"Remove a submodule properly
Removal touches four places. Missing any one leaves the repository in a confusing half-state.
step 5git submodule deinit -f path/to/submodule git rm -f path/to/submodule rm -rf .git/modules/path/to/submodule git commit -m "Remove submodule"
Why this works
A submodule isn't a copy of another project — it's a single line in the parent's tree recording a repository URL and one commit hash. Cloning fetches that pointer, but the pointed-to repository is a separate download that Git won't perform unless asked, which is why the folder is created and left empty. `--init` reads .gitmodules into your local config and `update` clones and checks out the pinned commit, which is also why a submodule always lands on a specific commit rather than a branch.
How to stop it happening again
- Set submodule.recurse true globally so clone, pull and checkout handle submodules automatically.
- Document the init command in your README for anyone who hasn't.
- Consider whether a package manager or git subtree would serve you better - submodules add real friction.
Commands used in this guide
git submoduleEmbed another Git repository inside yours at a pinned commit.
git cloneCopy a remote repository, its full history and its branches, onto your machine.
git subtreeVendor another repository into a subdirectory - without submodules' friction.
git configRead and write Git settings for one repo, your user, or the whole machine.
Still stuck?
Search the full command reference and every other rescue guide — there are 76 of them, covering everything from detached HEAD to force-push disasters.
Browse all guides →Frequently asked questions
Why are my submodule folders empty after cloning?
A clone records the submodule pointer but doesn't download the referenced repository. Run git submodule update --init --recursive to fetch and check out each one at the commit your repository pins.
How do I clone a repository with all its submodules at once?
git clone --recurse-submodules <url>. To make it automatic for every future command, set git config --global submodule.recurse true, which also applies to pull and checkout.
Why does my submodule show as modified when I didn't change it?
The submodule is checked out at a different commit than the parent repository records - often after building inside it or pulling within it. Either reset it with git submodule update --init --force, or commit the new pointer deliberately if the update was intended.
Related rescue guides
How do I remove a Git submodule — and why does re-adding it say it already exists in the index?
git submodule deinit -f <path>, then git rm -f <path>, then delete .git/modules/<path>. All three matter: deinit clears the config, git rm removes the gitlink and the .gitmodules entry, and the leftover .git/modules copy is what makes re-adding it fail with 'already exists in the index'.
Read the fix →Always fixableCloning takes forever — how do I clone a big repo faster?
Use --filter=blob:none for a partial clone that fetches file contents lazily, and add sparse-checkout to materialise only the directories you need. --depth 1 is fastest of all but gives you no usable history.
Read the fix →Always fixableHow do I combine two separate repos while keeping both histories?
Add the second repository as a remote, fetch it, and merge with --allow-unrelated-histories into a subdirectory. Both sets of commits end up in one repository with their history intact.
Read the fix →Always fixableGit LFS is broken — my files are text pointers, or the clone dies with 'smudge filter lfs failed'
A smudge failure means Git downloaded the pointer files but could not fetch the real content. Clone with GIT_LFS_SKIP_SMUDGE=1 to get past it, run git lfs install to register the filters, then git lfs pull to fetch the actual bytes. If you are seeing a 130-byte text file where a binary should be, the pointer was never expanded — the content is safe on the server.
Read the fix →