Fix GitHub Actions Private Submodule Checkout Errors
ملخص
Pixel’s announcement explains why GitHub Actions can check out the main repository but fail when fetching a private submodule. It covers repository-scoped GITHUB_TOKEN access, the effects of persist-credentials: false, and how these credential issues differ from a corrupt submodule pointer.
DEV Community
GitHub Actions submodule checkout failed on a private submodule
GitHub Actions submodule checkout failed is a credential message rather than a git one: the outer repository clones cleanly and only the submodule fetch stops. The credential the action configured is scoped to the repository running the workflow, and a submodule usually lives somewhere else. Two independent decisions: what the .gitmodules URL looks like, and whether checkout was allowed to persist a credential at all. What this error means The checkout step is green for several seconds, prints the submodule update command, starts cloning the first submodule and then stops. Git asks for a username, finds it cannot prompt because the runner has no terminal, and gives up. The outer repository is fully checked out on disk, which is what makes this read like a corrupt submodule pointer rather than a permissions problem. Remove submodules from the step and everything is green again, which is the strongest hint that nothing about the repository itself is wrong. ```Quoted from actions/checkout#738, with the reporter's own redaction /usr/bin/git -c protocol.version=2 submodule update --init --force --depth=1 --recursive Cloning into ''... Error: fatal: could not read Username for 'https://github.com/': terminal prompts disabled
Common causes
The automatic token cannot read the submodule repository
The commonest cause and the one the error never names. GITHUB_TOKEN is an installation token whose permissions, in the documentation's words, are limited to the repository that contains your workflow. Raising permissions: contents: read does nothing here, because the limit is which repository, not which scope.
Credential persistence is switched off
With persist-credentials: false the action skips the whole of configureSubmoduleAuth, so no insteadOf rule and no core.sshCommand is written for any submodule. The outer clone still succeeds because the action fetches it directly, which is why this looks like a submodule-only fault.
An SSH URL with no key to match it
A .gitmodules entry starting [email protected]: is rewritten to HTTPS when no ssh-key is supplied, so the fetch then depends on the token like any other. If you expected SSH to be used, the failure message mentioning an HTTPS URL is the tell that the rewrite happened.
The submodule is nested and only one level was requested
Setting submodules: true initializes one level. A submodule inside a submodule is never fetched, so a later build step reports a missing directory rather than an authentication failure. Less common, and worth ruling out only after the credential is known to be right.
How to fix it
Give the step a token that can read the other repository
Create a fine-grained token or a GitHub App installation token with read access to the submodule repositories, store it as a secret and pass it as the token input. The action configures it for the outer repository and, through the insteadOf rule, for the submodules too.
.github/workflows/ci.yml (illustrative)
- uses: actions/checkout@v5
with:
submodules: recursive
token: ${{ secrets.SUBMODULE_TOKEN }}
Or supply a deploy key and keep the SSH URLs Add a deploy key to the submodule repository with read access. Store the private half as a secret in the repository that runs the workflow. Pass it as ssh-key, which makes the action set core.sshCommand inside each submodule. Leave the .gitmodules URLs as SSH: with a key present they are no longer rewritten. Check whether you turned persistence off Search the workflow for persist-credentials. If it is false, either remove it for the job that needs submodules, or keep it and authenticate the submodules yourself before the fetch. Turning it back on for one job is usually the smaller risk, because the action removes the credential in its post-job step either way..github/workflows/ci.yml (illustrative) uses: actions/checkout@v5 with: submodules: recursive persist-credentials: true ``` Standardi...

Powered by Readybot.io