How to Troubleshoot Issues with actions/checkout: Complete Debugging Guide

Enable step-debug logging by setting the ACTIONS_STEP_DEBUG secret to true to expose the internal Git commands and authentication headers, then trace failures through the specific helper modules—src/input-helper.ts, src/git-auth-helper.ts, and src/git-command-manager.ts—to identify whether your issue stems from token scopes, fetch depth, or sparse-checkout compatibility.

The actions/checkout GitHub Action is the standard mechanism for cloning repositories into workflow runners, but failures can manifest as cryptic authentication errors, missing refs, or silent submodule skips. By understanding how the action parses inputs in src/input-helper.ts and executes Git commands via src/git-command-manager.ts, you can systematically troubleshoot issues with actions/checkout using the actual source implementation rather than guesswork.

Understanding the Checkout Execution Flow

The action follows a strict four-phase pipeline that determines where failures originate.

First, input parsing occurs in input-helper.getInputs(), which reads all workflow parameters—ref, token, ssh-key, fetch-depth, submodules, and others—logging resolved values via core.debug in src/input-helper.ts.

Next, the action registers a problem matcher (src/main.ts, lines 15‑19) that converts Git CLI errors into GitHub annotations visible in the workflow UI.

The source acquisition phase then calls git-source-provider.getSource() (invoked from src/main.ts line 21). This instantiates a GitCommandManager, configures authentication through GitAuthHelper.configureAuth(), and executes git init, git remote add, git fetch (with optional sparse-checkout and LFS handling), and finally checks out the requested ref.

Finally, post-run cleanup executes when stateHelper.IsPost is true (src/main.ts lines 32‑47), triggering git-source-provider.cleanup() to remove temporary credentials, SSH keys, and global Git configuration entries written during the job.

Common Failure Points and Diagnostic Steps

Authentication Failures (HTTP 401)

When you encounter Authentication failed or HTTP 401 errors, the root cause is typically an incorrect or missing token input, insufficient token scopes (the token needs repo access for private repositories), or a missing ssh-key for SSH-based clones.

To confirm the diagnosis, enable step-debug logging and look for the http.<url>.extraheader entry that GitAuthHelper writes to the Git config, visible at line 63 of src/git-auth-helper.ts. If the header is missing or malformed, your token input was not propagated correctly.

Fix: Use a Personal Access Token (PAT) with the minimum required repo scope, or ensure the ssh-key input contains a valid private key when cloning via SSH.

Missing Credentials for Subsequent Git Operations

The error fatal: could not read Username for 'https://github.com': No such device or address often appears when a workflow step runs after checkout and attempts to fetch submodules or push tags, but the authentication context has been removed.

This happens when persist-credentials is set to false, causing the action to remove the HTTP extra header immediately after the initial clone. You can verify this by checking the post-run logs for the message Removing HTTP extra header (handled around line 447 of src/git-auth-helper.ts).

Fix: Either keep persist-credentials: true (the default) to retain the token in .git/config for subsequent steps, or explicitly configure submodule authentication using the ssh-key input if you must disable credential persistence.

Shallow Clone Errors

If you see fatal: remote error: upload-pack: not our ref when checking out older commits or during git checkout HEAD~1, the repository was likely cloned with a shallow history. By default, fetch-depth is set to 1, which fetches only the latest commit.

In src/git-command-manager.ts, the fetch() method only appends the --depth flag when fetchDepth > 0 (lines 99‑101), confirming that a shallow clone was performed.

Fix: Set fetch-depth: 0 to fetch the full history, or specify a sufficient depth value (e.g., fetch-depth: 10) to include the required ancestor commits.

Sparse-Checkout Not Applied

When using the sparse-checkout input to clone only specific directories, the operation may silently fail if the runner’s Git version is older than 2.28, which is required for cone-mode sparse checkouts.

The action validates this in src/git-command-manager.ts (lines 131‑134), logging Minimum Git version required for sparse checkout… when the check fails.

Fix: Ensure your runner uses Git 2.28 or newer (GitHub-hosted runners include this by default). If you must use an older Git version, set sparse-checkout-cone-mode: false to use the non-cone legacy mode.

Submodule Checkout Failures

Submodule initialization fails when submodule URLs use SSH (git@github.com:) but no ssh-key is provided, or when the main repository’s authentication is removed before submodules are processed.

The action handles this in GitAuthHelper.configureSubmoduleAuth() (lines 155‑229 of src/git-auth-helper.ts), which writes authentication config into each submodule’s .git/config. Look for the log message Configuring submodule auth to confirm this step executed.

Fix: Provide a valid ssh-key in the workflow inputs, or omit the SSH key to allow the action to automatically convert SSH URLs to HTTPS using the provided token.

Git-LFS Files Missing

Large files tracked by Git LFS appear as pointer files instead of actual content when lfs: true is omitted, or when the runner’s git-lfs binary is older than version 2.1.

The version check occurs in GitCommandManager.initializeCommandManager() (lines 700‑712).

Fix: Explicitly set lfs: true in your checkout step, and verify that self-hosted runners have a recent git-lfs installation (GitHub-hosted runners include this by default).

Unsafe PR Checkout Blocked

Workflows triggered by pull_request_target or workflow_run events may fail to checkout pull request code from forks unless explicitly permitted, as this pattern can lead to privilege escalation attacks.

The security guard is implemented in src/unsafe-pr-checkout-helper.ts, which logs a warning when it blocks the operation.

Fix: Only after conducting a thorough security review, set allow-unsafe-pr-checkout: true to permit checking out the fork’s code in these specific trigger contexts.

Safe Directory Warnings

On self-hosted runners, Git may refuse to operate in the workspace directory with errors about safe.directory, preventing the clone from proceeding.

The action attempts to mitigate this in GitDirectoryHelper (lines 96‑115) by automatically adding the repository path to Git’s safe directory list.

Fix: Keep set-safe-directory: true (the default) to allow the action to configure this automatically, or manually configure the safe directory in your workflow if you manage Git configuration externally.

Debugging Techniques

When troubleshooting complex failures, use these systematic approaches to expose the action’s internal state:

  1. Enable step-debug logging – Set the repository secret ACTIONS_STEP_DEBUG to true (or export the environment variable ACTIONS_STEP_DEBUG=true). This reveals all core.debug output, including resolved inputs from input-helper.ts and the exact Git CLI arguments executed by GitCommandManager.execGit.

  2. Inspect problem matcher output – The action registers problem-matcher.json at startup (src/main.ts), which turns Git errors into annotated workflow logs. Check the "Annotations" section of your workflow run for structured error details.

  3. Review post-run cleanup logs – The cleanup phase runs after the job completes. If you see warnings from GitAuthHelper.removeAuth() (lines 333‑336), temporary credentials may not be properly removed, indicating a permissions issue on the runner.

  4. Validate the Git version – The action aborts early if the runner’s Git version is incompatible. Add a separate step running git --version to verify the environment meets the requirements for features like sparse-checkout.

  5. Use the debug input – Add debug: true to the checkout step inputs to force verbose Git output, which is passed directly to the underlying git commands by the action.

Practical Configuration Examples

Enabling Full Debug Logging


# .github/workflows/debug.yml

name: Debug Checkout
on: push
jobs:
  debug:
    runs-on: ubuntu-latest
    steps:
      - name: Enable step debug
        env:
          ACTIONS_STEP_DEBUG: true
        run: echo "Debug logging enabled"
      - name: Checkout with full history
        uses: actions/checkout@v4
        with:
          fetch-depth: 0
          submodules: true
          lfs: true

Resolving Authentication Problems

- name: Checkout private repository
  uses: actions/checkout@v4
  with:
    repository: my-org/private-repo
    token: ${{ secrets.PAT }}
    persist-credentials: true

Configuring Sparse Checkout

- name: Checkout only documentation
  uses: actions/checkout@v4
  with:
    sparse-checkout: |
      docs/
      README.md
    sparse-checkout-cone-mode: true

Handling Submodules with SSH

- name: Checkout with SSH submodules
  uses: actions/checkout@v4
  with:
    submodules: recursive
    ssh-key: ${{ secrets.SSH_PRIVATE_KEY }}
    ssh-strict: true

Allowing Unsafe PR Checkouts

- name: Checkout fork PR (pull_request_target)
  uses: actions/checkout@v4
  with:
    ref: ${{ github.event.pull_request.head.sha }}
    allow-unsafe-pr-checkout: true

Summary

  • Enable ACTIONS_STEP_DEBUG to expose the exact Git commands and authentication headers written by GitAuthHelper in src/git-auth-helper.ts.
  • Check persist-credentials when subsequent Git operations fail with authentication errors, as the token is removed from .git/config when set to false.
  • Set fetch-depth: 0 to resolve "not our ref" errors caused by shallow clones that exclude required commit history.
  • Verify Git version 2.28+ on self-hosted runners when using sparse-checkout features, or disable cone-mode for legacy compatibility.
  • Provide ssh-key when submodules use SSH URLs, or let the action convert them to HTTPS automatically by omitting the key.
  • Review post-run logs to confirm that GitAuthHelper.removeAuth() successfully cleaned up temporary credentials from the runner.

Frequently Asked Questions

How do I enable debug logging for actions/checkout?

Set the repository secret ACTIONS_STEP_DEBUG to true (or set the environment variable ACTIONS_STEP_DEBUG=true in your workflow). This exposes the internal core.debug statements in src/input-helper.ts and src/git-command-manager.ts, showing you the exact inputs received and the Git CLI commands executed with their arguments.

Why does my checkout fail with "Authentication failed" even when I provide a token?

The token likely lacks the required repo scope for private repositories, or the persist-credentials input was set to false in a previous step, removing the authentication header before subsequent Git operations. Check the debug logs for the http.extraheader entry written by GitAuthHelper.configureAuth() in src/git-auth-helper.ts to verify the token is being applied correctly.

How do I fix sparse-checkout errors on my self-hosted runner?

Ensure your runner has Git version 2.28 or newer, which is required for cone-mode sparse checkouts as implemented in src/git-command-manager.ts (lines 131‑134). If you cannot upgrade Git, set sparse-checkout-cone-mode: false to use the legacy non-cone mode, which is compatible with older Git versions.

Why are my Git LFS files not being downloaded?

You must explicitly set lfs: true in the checkout step inputs, and your runner must have git-lfs version 2.1 or newer installed. The action checks the LFS version in GitCommandManager.initializeCommandManager() (lines 700‑712) and will skip LFS operations if the binary is missing or too old.

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 →