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

> Troubleshoot actions/checkout issues with this complete debugging guide. Enable step debug logging and trace failures through helper modules for swift resolution.

- Repository: [GitHub Actions/checkout](https://github.com/actions/checkout)
- Tags: how-to-guide
- Published: 2026-07-03

---

**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`](https://github.com/actions/checkout/blob/main/src/input-helper.ts), [`src/git-auth-helper.ts`](https://github.com/actions/checkout/blob/main/src/git-auth-helper.ts), and [`src/git-command-manager.ts`](https://github.com/actions/checkout/blob/main/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`](https://github.com/actions/checkout/blob/main/src/input-helper.ts) and executes Git commands via [`src/git-command-manager.ts`](https://github.com/actions/checkout/blob/main/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`](https://github.com/actions/checkout/blob/main/src/input-helper.ts).

Next, the action registers a **problem matcher** ([`src/main.ts`](https://github.com/actions/checkout/blob/main/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`](https://github.com/actions/checkout/blob/main/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`](https://github.com/actions/checkout/blob/main/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`](https://github.com/actions/checkout/blob/main/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`](https://github.com/actions/checkout/blob/main/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`](https://github.com/actions/checkout/blob/main/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`](https://github.com/actions/checkout/blob/main/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`](https://github.com/actions/checkout/blob/main/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`](https://github.com/actions/checkout/blob/main/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`](https://github.com/actions/checkout/blob/main/input-helper.ts) and the exact Git CLI arguments executed by `GitCommandManager.execGit`.

2. **Inspect problem matcher output** – The action registers [`problem-matcher.json`](https://github.com/actions/checkout/blob/main/problem-matcher.json) at startup ([`src/main.ts`](https://github.com/actions/checkout/blob/main/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

```yaml

# .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

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

```

### Configuring Sparse Checkout

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

```

### Handling Submodules with SSH

```yaml
- 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

```yaml
- 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`](https://github.com/actions/checkout/blob/main/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`](https://github.com/actions/checkout/blob/main/src/input-helper.ts) and [`src/git-command-manager.ts`](https://github.com/actions/checkout/blob/main/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`](https://github.com/actions/checkout/blob/main/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`](https://github.com/actions/checkout/blob/main/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.