How actions/checkout Handles Repository Fetching and Authentication: A Technical Deep Dive
The actions/checkout GitHub Action clones repositories by orchestrating workspace preparation, temporary credential injection for HTTPS or SSH via git-auth-helper.ts, ref resolution, and controlled git fetch operations, followed by mandatory cleanup of all authentication artifacts.
The actions/checkout action is the standard mechanism for accessing repository code in GitHub Actions workflows. Understanding how it handles repository fetching and authentication is essential for securing CI/CD pipelines and optimizing performance. This article examines the actual TypeScript implementation in the official repository to reveal how the action manages credentials without leaking secrets, resolves references, and executes Git commands.
Entry Point and Orchestration in git-source-provider.ts
The entire checkout process is coordinated by the getSource() function in src/git-source-provider.ts (lines 18-31, 42-51, 74-84, 146-175). This orchestrator initializes the Git workspace, configures authentication, determines which references to fetch, and manages optional features like LFS and submodules.
The getSource Function Workflow
When the action runs, getSource() executes a strict sequence:
- Workspace Preparation: Creates or cleans the target directory to ensure a pristine environment.
- Git Manager Initialization: Instantiates a
GitCommandManagerto abstract low-level Git CLI operations. - Authentication Setup: Calls
authHelper.configureAuth()to prepare HTTPS or SSH credentials without exposing them in process arguments. - Ref Resolution: Uses
ref-helper.tsto convert user inputs (branch names, tags, or PR numbers) into precise Git ref-specs. - Fetch Execution: Invokes
git.fetch()with calculated options for depth, filters, and tags. - Post-Fetch Operations: Optionally initializes submodules, enables sparse checkout, or pulls LFS objects.
- Credential Cleanup: Removes all temporary authentication files and config entries.
Authentication Mechanisms in git-auth-helper.ts
All credential handling is encapsulated in the GitAuthHelper class within src/git-auth-helper.ts. This module ensures tokens and SSH keys never appear in shell history or process listings by writing sensitive data to temporary files under RUNNER_TEMP and referencing them via Git config includes.
Token-Based HTTPS Authentication
When using the default GitHub token or a personal access token, the helper builds an HTTP AUTHORIZATION: basic header using the provided authToken (lines 55-65). Rather than passing the token via command line, the action:
- Stores the header in a temporary credentials config file under
RUNNER_TEMP - References this file via
includeIf.gitdir:entries in the Git config (lines 260-285) - Restricts the include pattern to the repository directory to prevent credential leakage to other processes
This approach ensures the token is automatically injected only for requests targeting the specific repository and any worktrees derived from it.
SSH Key Authentication
For SSH-based authentication, the helper handles private keys through environment variables and temporary files:
- Writes the
sshKeyand optional known-hosts data to temporary files underRUNNER_TEMP(lines 55-66) - Sets the
GIT_SSH_COMMANDenvironment variable to invokessh -i <keyfile>(lines 101-119) - Configures
core.sshCommandin the Git config whenpersistCredentialsis true, ensuring submodules can authenticate using the same key
Global Authentication for Submodules
When submodules: true is specified, the action must authenticate recursive clone operations. The configureGlobalAuth() method (lines 128-149) writes the token or SSH settings into a temporary HOME directory with a custom global Git config. This isolated environment prevents credentials from persisting in the user's actual global Git configuration while allowing git submodule update to access private repositories.
Resolving References with ref-helper.ts
Before fetching, the action must translate user inputs into Git ref-specs. The src/ref-helper.ts module converts various reference types:
- Branch names: Expands to
+refs/heads/<branch>:refs/remotes/origin/<branch> - Pull requests: Creates
+<sha>:refs/remotes/pull/<id>for PR refs - Tags: When
fetchTagsis true, adds+refs/tags/*:refs/tags/*(lines 91-95)
After fetching, refHelper.testRef() validates that the fetched commit matches the expected SHA to prevent race conditions where a branch moves between resolution and fetch (lines 191-210 in git-source-provider.ts).
Executing the Fetch in git-command-manager.ts
The actual network operation occurs in src/git-command-manager.ts via the fetch() method (lines 277-298). This method constructs the Git command with protocol optimizations and performance flags:
const args = ['-c', 'protocol.version=2', 'fetch'];
if (options.fetchDepth && options.fetchDepth > 0) {
args.push(`--depth=${options.fetchDepth}`);
}
if (options.filter) {
args.push(`--filter=${options.filter}`);
}
await exec.exec('git', args.concat(refSpec));
Shallow Fetch and Filter Options
The action supports several fetch optimizations controlled by IGitSourceSettings:
fetchDepth: Implements shallow cloning (--depth=1by default) to reduce transfer sizefilter: Supports blob filtering (blob:none) for partial clonesfetchTags: Conditionally includes the tag ref-spec based on user configuration
When shallow fetching is requested, the action validates the resulting commit to ensure the ref still points to the expected SHA, protecting against the "shallow clone race condition" where the remote advances after the fetch but before the checkout.
Optional Post-Fetch Operations
After the initial fetch succeeds, git-source-provider.ts handles several optional features:
Git LFS and Sparse Checkout
- LFS: Runs
git lfs installfollowed bygit lfs fetch <ref>(lines 69-72) - Sparse Checkout: Configures cone-mode or non-cone-mode sparse checkout patterns by calling
git.sparseCheckout()orgit.sparseCheckoutNonConeMode()(lines 60-66), allowing users to materialize only specific directories
Submodule Initialization
When submodules are enabled (lines 74-85), the action executes:
git submodule syncto update submodule URLsgit submodule updateto fetch and checkout submodule content- Disables automatic garbage collection to prevent credential traces from being packed into
.gitobjects
For persisted credentials, authHelper.configureSubmoduleAuth() (lines 151-164) writes includeIf entries mapping submodule paths to the shared credentials config file, supporting both host and container path formats.
Security and Cleanup Procedures
Security relies on the guarantee that temporary credentials are removed after the job completes. The removeAuth() method (lines 332-425 in git-auth-helper.ts) deletes:
- SSH private key temporary files
- SSH known-hosts temporary files
- HTTP authorization header config files
Additionally, removeGlobalConfig() restores the original HOME environment variable, ensuring the temporary global Git config is no longer referenced by subsequent processes.
Practical Configuration Examples
Basic HTTPS Checkout with Shallow History
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 1
token: ${{ secrets.GITHUB_TOKEN }}
Uses automatic token injection via temporary HTTP headers without persisting credentials.
Full History Checkout with LFS
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
lfs: true
fetch-tags: true
Fetches all history and tag objects, then downloads LFS content after the initial clone.
SSH Authentication with Submodule Support
steps:
- uses: actions/checkout@v4
with:
ssh-key: ${{ secrets.SSH_PRIVATE_KEY }}
submodules: recursive
persist-credentials: true
Writes the SSH key to RUNNER_TEMP, sets GIT_SSH_COMMAND, and configures global SSH settings for nested submodules.
Sparse Checkout (Cone Mode)
steps:
- uses: actions/checkout@v4
with:
sparse-checkout: |
src/
docs/
sparse-checkout-cone-mode: true
Only materializes the src and docs directories, leaving other repository content unfetched.
Summary
- Orchestration:
src/git-source-provider.tscoordinates the entire flow viagetSource(), managing workspace preparation, authentication, fetching, and cleanup - Security:
src/git-auth-helper.tsprevents credential leakage by writing tokens and SSH keys to temporary files underRUNNER_TEMPand referencing them via Git config includes rather than command-line arguments - Ref Resolution:
src/ref-helper.tsconverts branches, tags, and PR numbers into precise Git ref-specs and validates fetched commits - Execution:
src/git-command-manager.tsexecutesgit fetchwith protocol version 2, shallow depth, and blob filters for optimized transfers - Cleanup: All authentication artifacts are removed after checkout, including temporary SSH keys and HTTP header configs, unless
persist-credentials: trueis explicitly set for submodule operations
Frequently Asked Questions
How does actions/checkout authenticate with GitHub repositories?
By default, actions/checkout uses the automatic GITHUB_TOKEN provided by the workflow runner. The action writes this token to a temporary credentials file under RUNNER_TEMP and configures Git to include an Authorization: basic header only for requests targeting the specific repository directory via includeIf.gitdir: directives. This prevents the token from appearing in process listings or shell history while ensuring only the intended repository receives the credentials.
What is the difference between token-based and SSH authentication in actions/checkout?
Token-based authentication uses HTTPS URLs with an injected Authorization header, suitable for most GitHub-hosted runners and requiring no additional key management. SSH authentication requires providing an ssh-key input, which the action writes to a temporary file and references via the GIT_SSH_COMMAND environment variable. SSH is necessary for accessing repositories in other providers or when specific key-based access controls are required, while tokens are preferred for GitHub-to-GitHub communication due to their automatic rotation and scoped permissions.
How does the fetch-depth parameter optimize repository fetching?
The fetch-depth parameter controls shallow cloning via the --depth Git flag. When set to 1 (the default), the action performs a shallow fetch containing only the latest commit, significantly reducing clone time and disk usage for large repositories. Setting fetch-depth: 0 disables shallow fetching and retrieves full history. The action validates that shallowly-fetched commits match expected SHAs to prevent inconsistencies when refs advance on the remote during the fetch operation.
Are credentials persisted after the checkout step completes?
By default, credentials are not persisted. The action automatically removes temporary SSH keys, known-hosts files, and HTTP authorization configs after the job finishes. However, if persist-credentials: true is set—typically required when checking out submodules that reference private repositories—the action retains the credentials config and SSH settings so subsequent steps (like git submodule update) can authenticate. These persisted credentials are still scoped to the temporary config files and cleaned up when the job container is destroyed.
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 →