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", both result.submodules and result.nestedSubmodules become true
  • If the value equals "TRUE", only result.submodules becomes true
  • 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.
  • true mode: Fetches only top-level submodules using git submodule update --init --force.
  • recursive mode: Fetches the complete submodule tree including nested dependencies using the --recursive flag.
  • Implementation: src/input-helper.ts parses the input into boolean flags, while src/git-source-provider.ts executes 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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →