actions/checkout Submodules Default Behavior: A Complete Guide

By default, actions/checkout ignores submodules unless explicitly configured with submodules: true or submodules: recursive.

The actions/checkout repository provides the official GitHub Action for cloning repositories in CI/CD workflows. Understanding how this action handles Git submodules is critical for projects that depend on external libraries or nested repositories, as the default configuration skips submodule initialization entirely to optimize checkout speed.

The Three Submodule Modes

The submodules input accepts three distinct values that control fetch behavior:

  • false (default) — Submodules are completely ignored; only the parent repository is checked out.
  • true — Top-level submodules are fetched and updated, but nested submodules (submodules within submodules) are skipped.
  • recursive — All submodules are fetched recursively, including unlimited nesting levels.

Implementation Details

Input Parsing in input-helper.ts

The action normalizes the submodules input in src/input-helper.ts (lines 127-138), converting the string value into boolean flags that control the checkout flow:

// src/input-helper.ts
const submodulesString = (core.getInput('submodules') || '').toUpperCase()
if (submodulesString == 'RECURSIVE') {
  result.submodules = true
  result.nestedSubmodules = true
} else if (submodulesString == 'TRUE') {
  result.submodules = true
}

When submodules is set to recursive, both submodules and nestedSubmodules flags are enabled. Any other value (including empty string or false) leaves submodules as false, triggering the default behavior where submodule operations are skipped.

Checkout Flow in git-source-provider.ts

The main orchestration logic resides in src/git-source-provider.ts (lines 74-97), where submodule operations execute only when settings.submodules evaluates to true:

// src/git-source-provider.ts
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()
}

This conditional block ensures that when using the default submodules: false, the action performs no submodule-specific Git operations, avoiding unnecessary network requests and authentication complications.

Git Command Execution in git-command-manager.ts

Actual Git operations are abstracted in src/git-command-manager.ts through three key methods:

  1. submoduleSync(recursive: boolean) — Executes git submodule sync to align submodule URLs with the parent repository's configuration.
  2. submoduleUpdate(fetchDepth: number, recursive: boolean) — Runs git submodule update --init --force, optionally adding --depth for shallow clones and --recursive for nested submodules.
  3. submoduleForeach(command: string, recursive: boolean) — Applies configuration commands (such as disabling garbage collection) across all initialized submodules.

Submodule Detection and Cleaning

In src/git-directory-helper.ts, the action uses git submodule status (lines 84-85) to detect existing submodules when determining whether to clean the working directory. This prevents accidental deletion of submodule content during incremental checkouts, even when the submodules input is disabled.

Authentication and Credentials

When persist-credentials is enabled, the action extends authentication to submodules after fetching. In src/git-source-provider.ts (lines 92-96):

if (settings.persistCredentials) {
  core.startGroup('Persisting credentials for submodules')
  await authHelper.configureSubmoduleAuth()
  core.endGroup()
}

This ensures that subsequent steps in your workflow can push to submodule remotes using the same authentication tokens configured for the parent repository.

Usage Examples

Default Behavior (Submodules Ignored)

By default, submodules are not fetched. This is the most efficient option for repositories without submodules:

- uses: actions/checkout@v4
  # submodules defaults to false

Top-Level Submodules Only

Fetch immediate submodules without recursion:

- uses: actions/checkout@v4
  with:
    submodules: true

Recursive Submodule Checkout

Fetch all nested submodules with full history:

- uses: actions/checkout@v4
  with:
    submodules: recursive
    fetch-depth: 0

Persist Credentials for Submodules

Enable authentication persistence for submodule operations:

- uses: actions/checkout@v4
  with:
    submodules: recursive
    persist-credentials: true

Summary

  • Default behavior (submodules: false) ignores all submodules to optimize checkout performance.
  • Top-level fetching (submodules: true) initializes only immediate submodules using git submodule update --init.
  • Recursive fetching (submodules: recursive) passes the --recursive flag to fetch unlimited nesting levels.
  • Implementation spans src/input-helper.ts for parsing, src/git-source-provider.ts for orchestration, and src/git-command-manager.ts for Git CLI execution.
  • Authentication configuration propagates to submodules when persist-credentials is enabled.

Frequently Asked Questions

What is the default behavior for submodules in actions/checkout?

By default, actions/checkout sets submodules to false, meaning Git submodules are completely ignored during the checkout process. Only the parent repository content is fetched, which minimizes network usage and checkout time for repositories that do not require external dependencies.

How do I fetch nested submodules recursively?

Set the submodules input to recursive in your workflow configuration. According to the source code in src/input-helper.ts, this value sets both the submodules and nestedSubmodules flags to true, which triggers git submodule update --init --recursive through the GitCommandManager.submoduleUpdate method.

Why are my submodules not updating with actions/checkout?

If submodules are not updating, verify that you have explicitly set submodules: true or submodules: recursive. The default behavior skips submodule initialization entirely. Additionally, ensure your workflow has appropriate permissions and that the fetch-depth parameter accommodates your submodule history requirements.

Does actions/checkout persist credentials for submodules?

Yes, when persist-credentials is set to true, the action configures authentication for submodules via authHelper.configureSubmoduleAuth() in src/git-source-provider.ts. This allows subsequent workflow steps to interact with submodule remotes using the same credentials established for the parent repository.

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 →