GitLab CI Error Guide: 'No url found for submodule path' — Fix Submodule Cloning
Fix 'fatal: No url found for submodule path in .gitmodules' in GitLab CI: set GIT_SUBMODULE_STRATEGY, sync .gitmodules URLs, use relative paths or CI_JOB_TOKEN, and authenticate private submodules correctly.
- #gitlab
- #ci-cd
- #troubleshooting
- #errors
Stuck on this GitLab CI/CD error? Get the free incident triage checklist
A one-page PDF — the exact steps to isolate, fix, and verify a production error like this one. No spam, unsubscribe anytime.
Overview
GitLab Runner clones your repository at the start of every job. When your project uses Git submodules and the runner tries to initialize them, it fails if the path recorded in the tree has no matching URL entry in .gitmodules:
fatal: No url found for submodule path 'libs/shared' in .gitmodules
ERROR: Job failed: exit status 128
You will also see the closely related variant when the submodule is registered but never checked out, because GIT_SUBMODULE_STRATEGY was left at its default:
Skipping Git submodules setup
Both point at the same root problem: GitLab’s submodule handling is driven by the .gitmodules file and the GIT_SUBMODULE_STRATEGY variable, and one of them is out of sync with what is committed in the tree.
Symptoms
- The job fails during the “Getting source from Git repository” phase with
exit status 128, before any of yourscript:runs. fatal: No url found for submodule path '<path>' in .gitmodulesnames a directory that exists as a gitlink in the tree.- Submodule directories are empty at build time and your build fails with “file not found” for code that lives in a submodule.
- The error appears only in CI, while local
git clone --recurse-submodulesworks because your local.git/configstill holds a URL that was never committed. - After moving or renaming a submodule, the path in the tree and the entry in
.gitmodulesdisagree.
Common Root Causes
.gitmodulesmissing or stale — a submodule was added, moved, or removed but.gitmoduleswas not committed with a matching[submodule "<path>"]/url =entry.GIT_SUBMODULE_STRATEGYunset — the default isnone, so submodules are never fetched; a build then trips over empty directories (the “Skipping Git submodules setup” case).- Absolute submodule URLs to a private host —
.gitmodulesuseshttps://gitlab.example.com/group/dep.git, and the runner has no credentials for it, so the fetch is rejected. - Path/URL mismatch after a rename — the gitlink path in the tree was changed but the
[submodule "..."]stanza key orpath =value was not updated. - Submodule on a different host or an SSH URL — CI cannot use your local SSH keys, so an
git@-style URL cannot authenticate. - Nested submodules without
GIT_SUBMODULE_STRATEGY: recursive, so second-level submodules are missed.
Diagnostic Workflow
First confirm what the tree records versus what .gitmodules declares. A gitlink with no .gitmodules entry is the exact cause of the error:
git ls-files --stage | grep ^160000 # 160000 = gitlink (submodule) entries
git config -f .gitmodules --list # every path=/url= actually committed
Every path printed by the first command must have a matching submodule.<name>.path and submodule.<name>.url in the second. If a path is missing, that is your failure.
Reproduce the runner’s clone locally with the same strategy CI uses:
git submodule sync --recursive
git submodule update --init --recursive
Set the strategy explicitly in .gitlab-ci.yml and prefer relative URLs so submodules resolve against the same GitLab instance and inherit the job token:
variables:
GIT_SUBMODULE_STRATEGY: recursive
GIT_SUBMODULE_FORCE_HTTPS: "true" # rewrite git@/ssh submodule URLs to HTTPS + CI_JOB_TOKEN
build:
stage: build
script:
- ls -la libs/shared # verify the submodule is populated
- make build
Always validate the pipeline configuration itself with the CI Lint tool (Project → Build → Pipeline editor → Validate, or /-/ci/lint) before pushing, so a YAML mistake is not masking the real submodule error.
If the submodule lives in a private project, ensure the URL is relative in .gitmodules so CI_JOB_TOKEN can authenticate it:
# .gitmodules — relative path resolves against the parent repo's host
git config -f .gitmodules submodule.libs/shared.url ../../group/shared.git
git add .gitmodules
git commit -m "Use relative submodule URL for CI"
Example Root Cause Analysis
A team renamed a submodule directory from vendor/auth to libs/auth using git mv. Locally everything worked because their .git/config was already initialized. In CI, every pipeline failed with:
fatal: No url found for submodule path 'libs/auth' in .gitmodules
ERROR: Job failed: exit status 128
Running git ls-files --stage | grep ^160000 showed a gitlink at libs/auth, but git config -f .gitmodules --list still listed submodule.vendor/auth.path=vendor/auth. The git mv moved the gitlink in the tree but did not rewrite .gitmodules, so the runner found a submodule path with no declared URL.
The fix was to correct the stanza and re-sync:
git config -f .gitmodules --rename-section submodule.vendor/auth submodule.libs/auth
git config -f .gitmodules submodule.libs/auth.path libs/auth
git submodule sync --recursive
git add .gitmodules
git commit -m "Fix .gitmodules path after submodule rename"
With GIT_SUBMODULE_STRATEGY: recursive already set, the next pipeline cloned the submodule cleanly and the build passed.
Prevention Best Practices
- Always commit
.gitmodulesin the same change that adds, moves, or removes a submodule; usegit mvplus a manual.gitmodulesedit andgit submodule sync. - Set
GIT_SUBMODULE_STRATEGY: recursiveexplicitly in.gitlab-ci.ymlrather than relying on thenonedefault, and useGIT_SUBMODULE_DEPTHto speed up large trees. - Prefer relative submodule URLs (
../../group/dep.git) so they resolve against the same GitLab instance and authenticate withCI_JOB_TOKENautomatically. - Enable
GIT_SUBMODULE_FORCE_HTTPS: "true"sogit@/SSH submodule URLs are rewritten to HTTPS in CI, where SSH keys are not available. - For private cross-project submodules, verify the target project allows the parent project via the CI/CD job token allowlist.
- Validate every pipeline change with CI Lint before pushing, and add a smoke job that runs
lson submodule paths so an empty submodule fails fast with a clear message.
Quick Command Reference
# List submodule gitlinks recorded in the tree
git ls-files --stage | grep ^160000
# List what .gitmodules actually declares
git config -f .gitmodules --list
# Re-sync configured URLs and re-init submodules
git submodule sync --recursive
git submodule update --init --recursive
# Rewrite a stale stanza after a rename
git config -f .gitmodules --rename-section submodule.OLD submodule.NEW
git config -f .gitmodules submodule.NEW.path NEW/path
# Set a relative URL that works with CI_JOB_TOKEN
git config -f .gitmodules submodule.NEW.url ../../group/dep.git
# .gitlab-ci.yml — reliable submodule handling
variables:
GIT_SUBMODULE_STRATEGY: recursive
GIT_SUBMODULE_FORCE_HTTPS: "true"
GIT_SUBMODULE_DEPTH: 1
Conclusion
No url found for submodule path almost always means the tree contains a submodule gitlink that .gitmodules does not describe — usually after an add, move, or rename where .gitmodules was left behind. Confirm the mismatch with git ls-files --stage | grep ^160000 against git config -f .gitmodules --list, correct the stanza, run git submodule sync, and set GIT_SUBMODULE_STRATEGY: recursive with relative HTTPS URLs so CI clones and authenticates submodules the same way every time.
Fixed it? Get 500 GitLab CI/CD & DevOps AI prompts — free
500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.
- 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
- Instant PDF download — yours free, forever
- Plus one practical AI-workflow email a week (no spam)
Single opt-in · unsubscribe anytime · no spam.
Did this fix your issue?
Get 500 Battle-Tested DevOps AI Prompts — Free
500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.
- 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
- Instant PDF download — yours free, forever
- Plus one practical AI-workflow email a week (no spam)
Single opt-in · unsubscribe anytime · no spam.