Empty submodule after git clone: initialize it and verify the recorded commit
By ON Lab · Sources checked November 8, 2026
A normal clone can leave submodule directories without their files. In an existing clone, inspect .gitmodules and git submodule status, then use git submodule update --init --recursive when you intend to populate the registered submodules. The default checkout uses the commits recorded by the parent project, not automatically each submodule's newest branch tip.
The reader question describes a submodule that was committed in a parent repository but appeared empty after a plain clone. We reproduced the behavior with three local repositories and a fresh clone, including one nested submodule. No hosted project, credentials or existing workspace repository was changed.
Confirm that the path is actually a submodule
git status --short
git submodule status
git ls-tree HEAD -- modules/child
Run these separately and replace modules/child with the intended path. Read the matching entry in .gitmodules too. A tracked submodule is represented by a gitlink entry, commonly shown with mode 160000, along with the registered path information. An arbitrary empty folder, ignored dependency directory or incomplete file download is not automatically a submodule.
The submodule status output adds useful state: the current Git reference documents a leading minus for an uninitialized submodule, a plus when its checked-out commit differs from the parent's recorded commit, and U for a merge conflict. Do not treat all three as the same empty-folder case.
Populate an existing clone
From the parent project's root, after reviewing its submodule paths and URLs and preserving any local work, run:
git submodule update --init --recursive
The initialization step registers submodules that have not been set up locally. Updating fetches missing data and prepares their working trees according to the parent project's expectations. Recursion applies the process to nested submodules too. Existing submodule configuration can affect the chosen update procedure; read the project guidance rather than treating the command as a forced reset.
This can require network access to each submodule's own repository. Access to the parent does not establish access to every dependency repository. If the command fails, retain the exact path, URL category and error message. Do not report the directory as successfully populated from the parent clone's success alone.
For a new clone, Git also provides --recurse-submodules. Use the project's documented clone URL and inspect the result afterward. You do not need to reclone a working parent repository solely because its submodules were not initialized initially.
What our nested local test showed
Our leaf repository contained a text file. A middle repository included the leaf as a nested submodule. A parent repository included the middle as modules/child. After recording that relationship, we made an additional commit in the source middle repository without updating the parent's recorded reference.
| Operation | Observed files | Commit result |
|---|---|---|
| Plain clone of the parent | The child path lacked the middle repository's test file | Status marked the submodule uninitialized |
| Update with init, without recursion | The middle test file appeared; the nested leaf file did not | The first-level submodule was populated |
| Update with init and recursion | Both the middle and nested leaf test files appeared | The child HEAD matched the parent's recorded commit |
| Compare the source middle repository's newer tip | The newer source-only test file was not the expected checkout | The recorded child commit differed from that newer upstream tip |
We checked the actual file contents and full commit identifiers after the update. The submodule's HEAD was detached in the default checkout path, which the official reference documents as expected. That is different from losing a developer's own unnamed commit.
The local fixture required a per-command allowance for its own trusted file-based repositories. That test detail is not a recommendation to change a global transport policy for unfamiliar repositories.
Verify the recorded revision, not just a filled folder
git submodule status --recursive
git ls-tree HEAD -- modules/child
git -C modules/child rev-parse HEAD
git -C modules/child status --short
Compare the expected gitlink commit with the child's actual HEAD. Inspect the files the project needs and any local changes. A folder containing files can still be at the wrong revision; conversely, a detached child HEAD can be the correct recorded checkout.
Do not add --remote merely to make the command sound more complete. It changes the source of the target revision toward the submodule's remote-tracking branch, instead of the parent's recorded commit. Updating a dependency to a new revision is a separate development decision.
If initialization does not finish
- If the path is not registered, investigate how the parent records that dependency.
- If an access or network request fails, preserve the failing submodule's error separately from the parent clone result.
- If nested files remain missing, inspect the recursive status and the nested registrations.
- If local changes prevent an update, preserve those changes instead of adding a force option blindly.
- If the recorded commit is unavailable, retain its identifier and the remote error for the project maintainer.
This guide does not repair deleted remote commits, grant private-repository access or validate a build. Its completion test is specific: intended registered submodules contain the expected files at the parent-recorded revisions, with local work accounted for.
If you made your own commit while detached and need to retain it, read the separate detached-commit preservation guide before changing revisions. A normal dependency checkout and a developer's unretained work are different situations.