How actions/checkout Handles Git Submodules: Implementation and Configuration
The actions/checkout action handles Git submodules through three configurable modes that execute native Git commands to synchronize and update submodule references after cloning the parent repository.
The actions/checkout action is the standard method for checking out code in GitHub Actions workflows. When your repository contains Git submodules, understanding exactly how actions/checkout handles submodules ensures your CI/CD pipelines correctly fetch all required dependencies.
Parsing the Submodule Input
The process begins in src/input-helper.ts, where the action parses the submodules input and normalizes it into two distinct settings: a boolean submodules flag and a nestedSubmodules recursion flag.
// src/input-helper.ts (lines 127-138)
const submodulesString = (core.getInput('submodules') || '').toUpperCase()
if (submodulesString == 'RECURSIVE') {
result.submodules = true
} else if (submodulesString == 'TRUE') {
result.submodules = true
}
core.debug(`submodules = ${result.submodules}`)
core.debug(`recursive submodules = ${result.nestedSubmodules}`)
This logic supports three distinct behaviors:
false(default): Submodules are ignored entirelytrue: Top-level submodules are fetched non-recursivelyrecursive: All submodules including nested ones are fetched recursively
The Submodule Checkout Flow
After the main repository fetch completes, src/git-source-provider.ts orchestrates the submodule operations. The action only proceeds with submodule handling when settings.submodules evaluates to true.
// src/git-source-provider.ts (lines 74-97)
if (settings.submodules) {
core.startGroup('Setting up auth for fetching submodules')
await authHelper.configureGlobalAuth()
core.endGroup()
core.startGroup('Fetching submodules')
await git.submoduleSync(settings.nestedSubmodules)
await git.submoduleUpdate(settings.fetchDepth, settings.nestedSubmodules)
await git.submoduleForeach(
'git config --local gc.auto 0',
settings.nestedSubmodules
)
core.endGroup()
}
The flow executes three critical Git operations in sequence:
submoduleSync: Aligns submodule URLs with the parent repository configuration usinggit submodule syncsubmoduleUpdate: Checks out the specific commits referenced by the parent repository usinggit submodule update --init --forcesubmoduleForeach: Disables automatic garbage collection within each submodule by runninggit config --local gc.auto 0
Git Command Implementation
The actual Git operations are delegated to GitCommandManager in src/git-command-manager.ts. This wrapper abstracts the native Git CLI calls into typed methods that respect the fetch-depth and recursive parameters:
submoduleSync(recursive: boolean): Executesgit submodule syncto update remote URLssubmoduleUpdate(fetchDepth: number, recursive: boolean): Runsgit submodule update --init --forcewith optional--depthand--recursiveflagssubmoduleForeach(command: string, recursive: boolean): Iterates through submodules executing the provided command
These methods ensure that shallow fetching and recursive traversal are handled according to the workflow inputs.
Authentication and Credential Persistence
Submodules often require the same authentication as the parent repository. When persist-credentials is set to true, the action propagates authentication configuration to each submodule after fetching:
// src/git-source-provider.ts (lines 92-96)
if (settings.persistCredentials) {
core.startGroup('Persisting credentials for submodules')
await authHelper.configureSubmoduleAuth()
core.endGroup()
}
This call to configureSubmoduleAuth() in src/git-auth-helper.ts ensures that subsequent Git operations within submodules—such as pushing changes back to remote servers—can authenticate using the same token or SSH key configured for the primary repository.
Edge Cases and Limitations
The implementation includes specific safeguards for edge cases:
- REST API Fallback: When Git is not available on the runner (forcing a fallback to the REST API), submodule support is explicitly disabled and the action throws an error rather than attempting partial submodule handling
- Worktree Cleaning: The
submoduleStatushelper insrc/git-directory-helper.tsdetects existing submodule states to determine whether the worktree requires cleaning before checkout, preventing conflicts with orphaned submodule directories
Configuration Examples
Configure submodule handling in your workflow using the submodules input:
# Fetch only top-level submodules
- uses: actions/checkout@v7
with:
submodules: true
# Recursive submodule checkout with full history
- uses: actions/checkout@v7
with:
submodules: recursive
fetch-depth: 0
# Disable submodules (default behavior)
- uses: actions/checkout@v7
# Persist credentials for submodule operations
- uses: actions/checkout@v7
with:
submodules: recursive
persist-credentials: true
Summary
- actions/checkout supports three submodule modes:
false(default),true(top-level only), andrecursive(nested submodules) - Implementation spans four key files:
input-helper.tsfor parsing,git-source-provider.tsfor orchestration,git-command-manager.tsfor Git CLI execution, andgit-auth-helper.tsfor credential propagation - Execution sequence: Sync URLs, update commits, disable GC—executed only after the parent repository fetch completes
- Authentication automatically propagates to submodules when
persist-credentialsis enabled, ensuring private submodule repositories remain accessible - Shallow fetching via
fetch-depthapplies to submodules when specified, optimizing clone performance for large repositories
Frequently Asked Questions
What happens if I don't specify the submodules input?
By default, actions/checkout ignores submodules entirely. The submodules input defaults to false, meaning the action clones only the parent repository without fetching any submodule content. This minimizes clone time and network usage for workflows that do not require external dependencies.
Does actions/checkout support recursive submodules?
Yes. Set submodules: recursive to fetch all submodules including nested ones. According to the source code in src/git-command-manager.ts, this passes the --recursive flag to git submodule update, ensuring that submodules within submodules are also initialized and updated.
How does authentication work for private submodule repositories?
The action reuses the same authentication mechanism used for the parent repository. When persist-credentials is true, src/git-auth-helper.ts configures authentication for each submodule via configureSubmoduleAuth(). This injects the same GitHub token or SSH key into each submodule's Git configuration, allowing access to private repositories without additional secrets.
Can I use a shallow fetch with submodules?
Yes. When you specify fetch-depth (for example, fetch-depth: 1), the action passes this depth parameter to submoduleUpdate() in src/git-command-manager.ts. However, shallow submodules may cause issues if subsequent build steps require full git history within those submodules. Use fetch-depth: 0 for full history when needed.
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 →