Submodule Checkout Modes in actions/checkout: A Complete Guide
The actions/checkout GitHub Action supports three distinct submodule checkout behaviors—false** (default), true (non-recursive), and recursive—controlled via the submodules input parameter.**
When you use the official actions/checkout action to clone a repository containing Git submodules, you can configure exactly how those nested repositories are fetched and initialized. Understanding these modes ensures your CI/CD workflows handle complex repository structures correctly without unnecessary overhead.
The Three Submodule Checkout Modes
According to the actions/checkout source code, the submodules input accepts three distinct states that determine whether Git runs git submodule update and with which flags.
No Submodule Checkout (Default)
When you omit the submodules input or provide any value other than true or recursive, the action skips submodule initialization entirely. This is the fastest option and suitable for repositories that do not contain submodules or where you explicitly want to ignore them.
The action leaves both result.submodules and result.nestedSubmodules flags as false in the internal settings object.
Non-Recursive Submodule Checkout
Setting submodules: true enables non-recursive checkout. In this mode, the action executes:
git submodule update --init --force
This command initializes and fetches only top-level submodules directly referenced in your main repository. Any submodules nested inside those submodules remain ignored. This mode strikes a balance between functionality and performance when you need immediate dependencies but not the entire dependency tree.
Recursive Submodule Checkout
Setting submodules: recursive enables full recursive checkout. The action executes:
git submodule update --init --recursive --force
This fetches all submodules, including nested sub-submodules, preserving the complete tree structure defined in your .gitmodules files. Use this mode for complex projects with deep dependency hierarchies, though be aware it increases clone time and network usage proportionally to the submodule depth.
Implementation in Source Code
The mode selection logic resides in src/input-helper.ts. The action reads the submodules input, converts it to uppercase, and sets boolean flags accordingly:
- If the value equals
"RECURSIVE", bothresult.submodulesandresult.nestedSubmodulesbecometrue - If the value equals
"TRUE", onlyresult.submodulesbecomestrue - Any other value leaves both flags
false
Later, src/git-source-provider.ts consumes these flags to conditionally execute Git commands:
if (settings.submodules) {
core.startGroup('Fetching submodules')
await git.submoduleSync(settings.nestedSubmodules)
await git.submoduleUpdate(settings.fetchDepth, settings.nestedSubmodules)
// …
}
This implementation ensures that the nestedSubmodules boolean directly controls whether the --recursive flag is appended to Git commands.
Configuration Examples
Default Behavior (No Submodules)
steps:
- uses: actions/checkout@v4
# submodules input omitted — nested repositories are ignored
Non-Recursive Checkout
steps:
- uses: actions/checkout@v4
with:
submodules: true
Recursive Checkout
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
Summary
- Default mode: Submodules are ignored entirely for maximum speed and simplicity.
truemode: Fetches only top-level submodules usinggit submodule update --init --force.recursivemode: Fetches the complete submodule tree including nested dependencies using the--recursiveflag.- Implementation:
src/input-helper.tsparses the input into boolean flags, whilesrc/git-source-provider.tsexecutes the appropriate Git commands based on those flags.
Frequently Asked Questions
What is the default behavior for submodules in actions/checkout?
By default, actions/checkout does not initialize or fetch any submodules. If you omit the submodules input or set it to any value other than true or recursive, the action skips all submodule-related Git commands entirely.
Does recursive submodule checkout impact CI performance?
Yes. Recursive checkout significantly increases network transfer and disk usage because it fetches the entire submodule graph. If your workflow only requires top-level dependencies, use submodules: true instead of recursive to reduce clone time and storage consumption.
Can I use fetch-depth with submodules?
Yes. When fetch-depth is specified, the action passes this parameter to the submodule update commands in src/git-source-provider.ts. However, shallow submodule clones require Git 2.36+ for reliable operation with nested submodules.
How do I troubleshoot submodule fetch failures?
Check that your workflow has explicit token permissions to access submodule repositories, especially when submodule URLs point to private repos. The action uses the same credentials for submodules as the main repository, so ensure your persist-credentials setting and token input grant appropriate access to all nested repositories.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →