# How actions/checkout Handles Submodule Checkout: A Deep Dive into the GitHub Action's Source Code

> Discover how actions/checkout handles submodule checkout by parsing input and executing Git commands. Explore the source code and understand the process.

- Repository: [GitHub Actions/checkout](https://github.com/actions/checkout)
- Tags: deep-dive
- Published: 2026-08-29

---

**actions/checkout performs submodule checkout by parsing the `submodules` input parameter, then executing a sequence of native Git commands including `git submodule sync`, `git submodule update --init`, and recursive configuration, all orchestrated through TypeScript helper classes in the repository.**

The `actions/checkout` GitHub Action is the standard mechanism for retrieving repository code within workflows. When your project relies on **Git submodules**, understanding how this action handles nested repository cloning becomes critical for CI/CD pipeline optimization. The implementation relies on a specific input parsing strategy followed by atomic Git operations managed through the action's internal command abstractions.

## Parsing the submodules Input Parameter

The workflow begins in [`src/input-helper.ts`](https://github.com/actions/checkout/blob/main/src/input-helper.ts), where the action interprets user configuration and normalizes it into boolean flags. The code converts the input to uppercase and maps specific strings to internal state:

- `settings.submodules` — Set to `true` when the user requests any submodule checkout (either `true` or `recursive`)
- `settings.nestedSubmodules` — Set to `true` only when requesting **recursive** submodule checkout

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

```

This normalization ensures that downstream components receive consistent boolean flags regardless of case variations in the YAML configuration.

## The Submodule Checkout Workflow

When `settings.submodules` evaluates to true, [`src/git-source-provider.ts`](https://github.com/actions/checkout/blob/main/src/git-source-provider.ts) triggers a four-stage workflow that modifies the Git environment, synchronizes submodule references, updates working directories, and manages credential persistence.

### Configuring Authentication for Submodules

Before fetching submodule content, the action must ensure that private submodule URLs are accessible. The `gitAuthHelper.configureGlobalAuth()` method injects authentication tokens or SSH keys into the global Git configuration, allowing the subsequent submodule commands to access protected repositories without interactive prompts.

### Synchronizing and Updating Submodules

The core checkout logic executes two primary Git operations through the command manager:

1. **`git.submoduleSync(settings.nestedSubmodules)`** — Executes `git submodule sync` with the `--recursive` flag when `nestedSubmodules` is enabled. This updates the submodule URLs in `.git/config` to match the paths defined in `.gitmodules`.

2. **`git.submoduleUpdate(settings.fetchDepth, settings.nestedSubmodules)`** — Runs the initialization and fetch command:

```bash
git -c protocol.version=2 submodule update --init --force [--depth=N] [--recursive]

```

The `--depth` parameter is conditionally added based on `settings.fetchDepth`, enabling shallow clones for faster checkout times. This implementation resides in [`src/git-command-manager.ts`](https://github.com/actions/checkout/blob/main/src/git-command-manager.ts).

### Disabling Garbage Collection

After initialization, the action iterates through all submodules to disable automatic garbage collection. The `git.submoduleForeach('git config --local gc.auto 0', settings.nestedSubmodules)` command applies this setting to each submodule (recursively when configured), preventing Git from performing background maintenance that could interfere with subsequent workflow steps.

### Persisting Credentials Across Steps

If `persist-credentials` is set to `true`, the action executes `authHelper.configureSubmoduleAuth()` in [`src/git-source-provider.ts`](https://github.com/actions/checkout/blob/main/src/git-source-provider.ts). This writes an `includeIf` directive to each submodule's local Git config, pointing to a temporary credentials file. This mechanism ensures that later workflow steps can execute authenticated Git commands within submodule directories without re-entering credentials.

The complete workflow block in [`src/git-source-provider.ts`](https://github.com/actions/checkout/blob/main/src/git-source-provider.ts) appears as:

```typescript
// 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()

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

```

## Low-Level Git Command Implementation

The [`src/git-command-manager.ts`](https://github.com/actions/checkout/blob/main/src/git-command-manager.ts) file encapsulates all raw Git invocations, providing methods specifically for submodule operations:

- **`submoduleSync(recursive: boolean)`** — Wraps `git submodule sync` with optional `--recursive`
- **`submoduleUpdate(fetchDepth: number, recursive: boolean)`** — Constructs the update command with conditional shallow fetch and recursion flags
- **`submoduleForeach(command: string, recursive: boolean)`** — Executes `git submodule foreach` to run arbitrary commands across all submodules

These methods abstract the command-line interface details while exposing the specific flags required for the action's functionality.

## Configuration Examples for Submodule Checkout

Use these YAML configurations to control submodule behavior in your workflows:

```yaml

# Checkout only top-level submodules (non-recursive)

- uses: actions/checkout@v4
  with:
    submodules: true
    fetch-depth: 1

```

```yaml

# Recursive submodule checkout with full history

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

```

```yaml

# Explicitly disable submodule checkout (default behavior)

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

```

## Summary

- **Input parsing** in [`src/input-helper.ts`](https://github.com/actions/checkout/blob/main/src/input-helper.ts) converts the `submodules` string input into boolean flags for standard and recursive checkout modes.
- **Authentication setup** occurs before fetching, configuring global Git credentials to access private submodule repositories.
- **Synchronization and update** operations in [`src/git-source-provider.ts`](https://github.com/actions/checkout/blob/main/src/git-source-provider.ts) execute `git submodule sync` and `git submodule update --init` with optional recursion and depth limits.
- **Garbage collection** is disabled in each submodule via `git config --local gc.auto 0` to prevent background maintenance interference.
- **Credential persistence** optionally writes temporary config includes to submodules when `persist-credentials` is enabled, removed during post-job cleanup unless specified otherwise.

## Frequently Asked Questions

### How do I perform a recursive submodule checkout in GitHub Actions?

Set the `submodules` input to `recursive` in your workflow configuration. According to the source code in [`src/input-helper.ts`](https://github.com/actions/checkout/blob/main/src/input-helper.ts), this value sets both the `submodules` and `nestedSubmodules` flags to `true`, which adds the `--recursive` flag to all submodule commands including sync, update, and foreach operations.

### Does actions/checkout support shallow cloning for submodules?

Yes. When you specify `fetch-depth` in your workflow, this value is passed to the `git submodule update` command via the `--depth` parameter in [`src/git-command-manager.ts`](https://github.com/actions/checkout/blob/main/src/git-command-manager.ts). Setting `fetch-depth: 1` creates shallow clones of submodules, significantly reducing checkout time for large repositories.

### How does the action handle authentication for private submodules?

The `gitAuthHelper` class in [`src/git-auth-helper.ts`](https://github.com/actions/checkout/blob/main/src/git-auth-helper.ts) configures global authentication before submodule fetching and optionally persists credentials to each submodule's local configuration. The `configureGlobalAuth()` method injects tokens into the Git configuration temporarily, while `configureSubmoduleAuth()` creates `includeIf` directives for cross-step credential availability when `persist-credentials` is true.

### Why does the action disable garbage collection in submodules?

The action executes `git config --local gc.auto 0` in each submodule via `git.submoduleForeach()` to disable automatic garbage collection. This prevents Git from running background maintenance processes that could lock repository objects or interfere with subsequent workflow steps that modify the submodule working trees.