Git checkout

Before any build step runs, by default the Buildkite agent checks out your source code from the repository. This happens automatically without the need for extra configuration, but you can use a checkout block in your pipeline YAML to customize the behavior. You can skip the checkout entirely, perform shallow clones, restrict which paths are checked out, and more.

Agent version requirement

The checkout pipeline YAML features described on this page require Buildkite agent v3.130.0 or newer. Agents running older versions ignore the checkout block and use their default checkout behavior.

This page explains each checkout option and when to use it. For the full attribute reference, see the command step checkout attributes.

How checkout works

When a Buildkite Pipelines job starts, the agent performs the following steps in order:

  1. Run pre-checkout hooks: Agent and plugin pre-checkout hooks run before checkout starts.
  2. Run the checkout: Unless a custom checkout hook overrides the default Git checkout, the agent:
    • Clones or fetches the repository: If a local copy exists, the agent fetches the latest changes. Otherwise, it clones the repository from scratch.
    • Cleans the working directory: The agent runs git clean to remove untracked files left by previous builds.
    • Checks out the target commit: The agent checks out the specific commit that triggered the build.
    • Initializes submodules: If submodules are enabled, the agent fetches them recursively.
  3. Run post-checkout hooks: Agent, repository, and plugin post-checkout hooks run after checkout completes.

Each checkout option described on this page affects one or more of these steps. For example, checkout.skip skips the default Git checkout (as well as any custom checkout hook), while checkout.depth modifies the clone and fetch behavior within the checkout step. checkout.skip only skips the Git checkout itself: checkout-related pre-checkout and post-checkout hooks from the agent or plugins can still run around the skipped work.

Pipeline-level and step-level configuration

In Buildkite Pipelines, the checkout block can appear in two places:

  • Pipeline level: Sets defaults that apply to every step in the pipeline.
  • Step level: Overrides the pipeline-level defaults for a specific step.

When both are present, each key is resolved independently. The step value takes precedence for any key it sets, and the pipeline value is inherited for keys the step leaves unset.

For flags, commit_verification, and sparse, an explicit entry in the step's env map takes precedence if it sets the same environment variable. For skip, submodules, lfs, and depth, the checkout value always takes effect.

Step-level ssh_secret

The ssh_secret key is step-level only. It is not inherited from a pipeline-level checkout block, so it must be set on each step that needs it.

The following example disables submodules at the pipeline level, sets a shallow depth on a step that can use one, and re-enables submodules for a full-history step:

pipeline.yml
checkout:
  submodules: false

steps:
  - label: "Unit tests"
    command: "make test"
    checkout:
      depth: 10

  - label: "Full history analysis"
    command: "make audit"
    checkout:
      submodules: true

In this pipeline, both steps inherit submodules: false from the pipeline level. The "Unit tests" step adds a shallow depth: 10, while the "Full history analysis" step re-enables submodules and, by not setting depth, checks out the full history. Set checkout.depth only on steps that can use a shallow clone: depth must be a positive integer, so it cannot be set at the pipeline level and then unset to 0 for a full-history step.

Skipping checkout

The checkout.skip key tells the agent to skip the Git checkout phase entirely. When set to true, the agent does not clone, fetch, or check out any code before running the step's command.

This is useful for steps that do not need source code:

  • Notification steps that send messages or update external systems
  • Utility steps that run self-contained scripts or container images

The key accepts a boolean (true or false) or the equivalent string. It is emitted as BUILDKITE_SKIP_CHECKOUT.

Because the repository is not checked out, the step's command must be self-contained. For example, a buildkite-agent pipeline upload without an argument looks for files such as .buildkite/pipeline.yml, which are not present in a skipped-checkout workspace. Generate the YAML inline and pipe it into the upload instead:

pipeline.yml
steps:
  - label: "Upload pipeline"
    command: |
      cat <<'YAML' | buildkite-agent pipeline upload
      steps:
        - command: "make test"
      YAML
    checkout:
      skip: true

Precedence

Multiple mechanisms can control whether the checkout is skipped. When more than one is set, the highest-priority setting wins:

  1. BUILDKITE_SKIP_CHECKOUT exported by an environment or pre-checkout hook (runs last, so it can override any earlier value)
  2. Step-level checkout.skip in pipeline YAML
  3. Pipeline-level checkout.skip in pipeline YAML
  4. BUILDKITE_SKIP_CHECKOUT set in a pipeline or step env block
  5. Agent --skip-checkout CLI flag
  6. Default (false — checkout runs)

Hook exports are listed first because environment and pre-checkout hooks run after the agent has already resolved checkout.skip from the pipeline YAML. A hook that sets or unsets BUILDKITE_SKIP_CHECKOUT therefore has the final say.

For the remaining levels, a higher-priority setting overrides a lower one. For example, a step-level checkout.skip: false overrides a pipeline-level checkout.skip: true.

Migrating from the BUILDKITE_SKIP_CHECKOUT environment variable

If you currently skip checkout by setting the BUILDKITE_SKIP_CHECKOUT environment variable, you can migrate to the native checkout.skip key and also use the other checkout features.

Before — environment variable pattern:

pipeline.yml
steps:
  - label: "Notify deploy"
    command: "curl -X POST https://example.com/webhook"
    env:
      BUILDKITE_SKIP_CHECKOUT: "true"

After — native checkout.skip key:

pipeline.yml
steps:
  - label: "Notify deploy"
    command: "curl -X POST https://example.com/webhook"
    checkout:
      skip: true

Shallow clones

The checkout.depth key performs a shallow clone with the specified number of commits. This appends --depth=N to both BUILDKITE_GIT_CLONE_FLAGS and BUILDKITE_GIT_FETCH_FLAGS, so the agent fetches only the most recent history.

Shallow clones are useful for large repositories where full history is not needed. They reduce checkout time and disk usage by downloading fewer objects from the remote.

When checkout.depth is set, submodules are still fetched at full depth. The shallow depth applies only to the main repository clone and fetch operations.

Limitations of shallow clones

Some Git operations require full history. Commands like git log across the entire history, git blame, and git merge-base may return incomplete or incorrect results with a shallow clone. If a step needs full history, omit checkout.depth for that step. checkout.depth must be a positive integer, so 0 is not a valid way to request full history.

pipeline.yml
steps:
  - label: "Build"
    command: "make build"
    checkout:
      depth: 50

Sparse checkout

The checkout.sparse key checks out only specified paths using the Git sparse checkout feature in cone mode. The agent populates only the listed paths in the working directory, while the local repository still retains the full commit history.

Sparse checkout is particularly valuable in monorepo workflows where individual steps only need specific directories. Instead of checking out the entire repository tree, each step can declare exactly which paths it needs, reducing disk usage and speeding up operations like IDE indexing and file watchers.

The sparse key is a map containing a single key, paths, which accepts a string or an array of strings. The value is emitted as BUILDKITE_GIT_SPARSE_CHECKOUT_PATHS.

Requirements and constraints

Sparse checkout requires Git 2.26 or later. On agents with an older Git version, the agent falls back to a full checkout. Submodules are not initialized when sparse checkout is enabled.

A step-level sparse.paths replaces the pipeline-level paths rather than merging with them, so each step must repeat any pipeline-level paths it still needs. The following example sets a pipeline-level default of .buildkite/, then has each step override it with its own paths (repeating .buildkite/ so it is not lost):

pipeline.yml
checkout:
  sparse:
    paths:
      - .buildkite/

steps:
  - label: "Build frontend"
    command: "make build"
    checkout:
      sparse:
        paths:
          - .buildkite/
          - frontend/

  - label: "Build backend"
    command: "make build"
    checkout:
      sparse:
        paths:
          - .buildkite/
          - backend/

For a comparison of sparse checkout with Git mirrors and other optimization strategies, see Git checkout optimization.

Migrating from the sparse checkout plugin

If you currently use the Sparse Checkout plugin to check out specific paths, you can migrate to the native checkout.sparse key. The native feature covers the core use case of cone-mode sparse checkout with no extra plugin configuration.

Before — sparse checkout plugin:

pipeline.yml
steps:
  - label: "Pipeline upload"
    command: "buildkite-agent pipeline upload"
    plugins:
      - sparse-checkout#v1.7.0:
          paths:
            - .buildkite

After — native checkout.sparse key:

pipeline.yml
steps:
  - label: "Pipeline upload"
    command: "buildkite-agent pipeline upload"
    checkout:
      sparse:
        paths:
          - .buildkite/

The plugin offers several options that the native feature does not cover:

  • no_cone: The plugin supports non-cone patterns through the --no-cone flag. The native feature uses cone mode only.
  • clean_checkout: The plugin can remove lock files, reset the repository, and clean untracked files before checkout. Use checkout.flags.clean to customize clean flags natively, or handle cleanup in a pre-checkout hook.
  • cleanup_sparse_state: The plugin can tear down sparse-checkout Git config after the job finishes so that subsequent non-sparse jobs on the same agent are not affected. The native feature manages sparse-checkout state automatically.
  • verbose: The plugin can enable bash execution tracing for debugging. Use the Verifying checkout options take effect approach instead.
  • skip_ssh_keyscan: The plugin can skip the ssh-keyscan step. This is not applicable to the native feature.
  • post_checkout.unshallow: The plugin can convert a shallow clone to a full-depth clone after checkout. Omit checkout.depth to get a full clone natively.

If you rely on non-cone patterns or other plugin-specific options that have no native equivalent, you can continue using the plugin.

Submodules

The checkout.submodules key controls whether the Buildkite agent fetches Git submodules during checkout. It accepts a boolean (true or false) or the equivalent string, and is emitted as BUILDKITE_GIT_SUBMODULES.

When omitted at both the pipeline and step level, the agent defaults to true, meaning submodules are fetched automatically.

Agent-level override

The agent's --no-git-submodules flag retains a hard veto over checkout.submodules. If an agent starts with that flag, it forces BUILDKITE_GIT_SUBMODULES=false regardless of the value set in the pipeline YAML, and the build log emits a protected-environment-variable notice.

Disabling submodules can speed up checkout when your repository has large or numerous submodules that are not needed for every step. Note that submodules are also automatically disabled when sparse checkout is enabled.

pipeline.yml
checkout:
  submodules: false

steps:
  - label: "Fast build"
    command: "make build"

  - label: "Integration tests"
    command: "make integration"
    checkout:
      submodules: true

Git LFS

The checkout.lfs key controls whether the Buildkite agent downloads Git LFS objects during checkout. It accepts a boolean (true or false) or the equivalent string, and is emitted as BUILDKITE_GIT_LFS_ENABLED.

When omitted at both the pipeline and step level, the agent uses its own --git-lfs-enabled configuration setting, which defaults to false.

Use checkout.lfs: true when your repository stores binary assets such as images, videos, or compiled artifacts using Git LFS, and your build steps need access to those files:

pipeline.yml
steps:
  - label: "Build with assets"
    command: "make build"
    checkout:
      lfs: true

You can enable LFS at the pipeline level as a default, then disable it for steps that do not need it:

pipeline.yml
checkout:
  lfs: true

steps:
  - label: "Build with assets"
    command: "make build"

  - label: "Run unit tests"
    command: "make test"
    checkout:
      lfs: false

Custom Git flags

The checkout.flags key lets you set custom flags for specific Git operations during checkout. It is a map with four valid keys: clone, fetch, checkout, and clean. Each key sets the corresponding BUILDKITE_GIT_*_FLAGS environment variable:

When pipeline-level and step-level flags are both present, step values win per key. Pipeline values are inherited for any key the step omits.

Common use cases include partial clones with --filter=blob:none (which downloads commit and tree objects but defers blob downloads until needed), adding --tags to fetch for downloading all tags alongside branch history, and customizing clean behavior.

Security consideration

These flags are passed directly to Git commands. In dynamic pipelines where step definitions may come from untrusted input, validate the flag values to prevent command injection.

pipeline.yml
steps:
  - label: "Blobless build"
    command: "make build"
    checkout:
      flags:
        clone: "--filter=blob:none"
        fetch: "-v --prune --tags"
        clean: "-ffxdq -e .cache/"

Common use cases

The following examples show typical scenarios where custom Git flags can speed up builds or work around repository constraints.

Blobless clone for large repositories: Download commit and tree objects during clone but defer blob downloads until Git needs them. This significantly reduces initial clone time for repositories with large files:

pipeline.yml
checkout:
  flags:
    clone: "--filter=blob:none"

Treeless clone for very large monorepos: Download only commit objects during clone, deferring both tree and blob objects. This is faster than a blobless clone but causes more on-demand fetches during checkout:

pipeline.yml
checkout:
  flags:
    clone: "--filter=tree:0"

Fetch tags for release builds: Add --tags to the default fetch flags so that all tags are downloaded alongside branch history. This is useful for steps that derive version numbers from Git tags:

pipeline.yml
checkout:
  flags:
    fetch: "-v --prune --tags"

Aggressive clean but preserve log files: Use -ffdx -e '*.log' to remove all untracked files and force-remove nested Git repositories, but preserve .log files between builds. This is useful for keeping build logs around for debugging while still starting from an otherwise clean state:

pipeline.yml
checkout:
  flags:
    clean: "-ffdx -e '*.log'"

Clear default checkout flags: Set the checkout flags to an empty string to remove the default -f flag. This preserves local modifications, which can be useful for debugging or incremental build workflows:

pipeline.yml
checkout:
  flags:
    checkout: ""

Migrating from environment variables to pipeline checkout flags

If you currently set BUILDKITE_GIT_*_FLAGS environment variables in your pipeline YAML, you can migrate to the native checkout.flags keys for clearer configuration and consistent precedence handling.

Before — environment variable pattern:

pipeline.yml
steps:
  - label: "Build"
    command: "make build"
    env:
      BUILDKITE_GIT_CLONE_FLAGS: "--filter=blob:none"
      BUILDKITE_GIT_FETCH_FLAGS: "--prune"
      BUILDKITE_GIT_CLEAN_FLAGS: "-ffdx -e '*.log'"

After — native checkout.flags keys:

pipeline.yml
steps:
  - label: "Build"
    command: "make build"
    checkout:
      flags:
        clone: "--filter=blob:none"
        fetch: "--prune"
        clean: "-ffdx -e '*.log'"

The environment variables continue to work, so you can migrate incrementally. When both a checkout.flags key and the corresponding environment variable are set in a step's env block, the env block value takes precedence.

SSH key from Buildkite Secrets

The checkout.ssh_secret key specifies the name of a Buildkite secret that contains an SSH private key. At job startup, the agent fetches the SSH key from Buildkite Secrets using this name and configures GIT_SSH_COMMAND to use it during the Git checkout.

This is useful when cloning private repositories that require different SSH keys per step, such as when a build step needs access to a separate dependency repository.

Unlike the other checkout keys, ssh_secret is step-level only. It is not inherited from a pipeline-level checkout block, so it must be set on each step that needs it.

The value must be a string that:

  • Starts with a letter
  • Contains only letters, numbers, and underscores
  • Does not start with buildkite or bk
pipeline.yml
steps:
  - label: "Build with private deps"
    command: "make build"
    checkout:
      ssh_secret: "NAME_OF_BK_SECRET"

Commit verification

The checkout.commit_verification key tells the Buildkite agent to verify that the commit being built exists on the specified branch. This security feature is a branch-commit verification check, not GPG or SSH signature verification. This check protects against a scenario where a bad actor tries to trick CI into building a malicious commit that exists on a branch as though it is actually a commit on your main branch.

Two modes are available:

  • strict: Fails the job when the agent determines the commit is not on the branch.
  • warn: Emits a warning in the build log without failing the job.

If the agent cannot complete the check (for example, because a shallow clone cannot be deepened), it warns and continues in both modes.

When omitted, the agent falls back to its own --git-commit-verification configuration setting.

The agent silently skips verification in several cases where it is either not possible or not meaningful:

  • Tag builds
  • Pull request builds
  • Builds where the commit is HEAD
  • Builds with no branch set
  • Builds using a custom refspec
pipeline.yml
steps:
  - label: "Deploy"
    command: "make deploy"
    checkout:
      commit_verification: "strict"

Migrating from checkout plugins

Buildkite Pipelines now supports several checkout features natively that previously required plugins. You can migrate to the native checkout options for simpler configuration and tighter integration with the agent.

Plugin Native equivalent When to keep the plugin
Plugin Skip Checkout Native equivalent checkout.skip — see Skipping checkout When to keep the plugin The native feature fully replaces this plugin.
Plugin Custom Checkout Native equivalent checkout.depth and checkout.flags — see Shallow clones and Custom Git flags When to keep the plugin The native features cover the plugin's --depth and flag customization. The plugin may still be useful if you need its other options not covered by native checkout.
Plugin Sparse Checkout Native equivalent checkout.sparse — see Sparse checkout When to keep the plugin The plugin supports non-cone patterns, aggressive cleanup, skipping ssh-keyscan, and verbose debug mode. The native feature uses cone mode only.
Plugin Branch Commit Native equivalent checkout.commit_verification — see Commit verification When to keep the plugin The native feature fully replaces this plugin.

Troubleshooting

Verifying checkout options take effect

To confirm that a checkout option is being applied, inspect the BUILDKITE_GIT_* environment variables in the build log. Add an echo command to your step to print the relevant variable:

pipeline.yml
steps:
  - label: "Debug checkout"
    command: |
      echo "Clone flags: $BUILDKITE_GIT_CLONE_FLAGS"
      echo "Fetch flags: $BUILDKITE_GIT_FETCH_FLAGS"
      echo "Checkout flags: $BUILDKITE_GIT_CHECKOUT_FLAGS"
      echo "Clean flags: $BUILDKITE_GIT_CLEAN_FLAGS"
      echo "Skip checkout: $BUILDKITE_SKIP_CHECKOUT"
      echo "Sparse paths: $BUILDKITE_GIT_SPARSE_CHECKOUT_PATHS"
      echo "Submodules: $BUILDKITE_GIT_SUBMODULES"
      echo "LFS enabled: $BUILDKITE_GIT_LFS_ENABLED"

Sparse checkout paths not found

If sparse checkout does not populate expected files in the working directory, check the following:

  • Path accuracy: Paths are case-sensitive and must match the repository directory structure exactly. A trailing slash is optional. The path must reference a directory that exists in the repository.
  • Git version: Sparse checkout requires Git 2.26 or later. Older versions silently fall back to a full checkout without reporting an error. Run git --version on the agent to confirm.
  • Cone mode restrictions: The native checkout.sparse feature uses cone mode, which only accepts directory paths and their ancestors. Glob patterns and individual file paths within directories are not supported. If you need non-cone patterns, use the Sparse Checkout plugin with the no_cone option instead.
  • Step-level override: A step-level sparse.paths replaces the pipeline-level paths entirely rather than merging with them. Verify that each step lists all the paths it needs, including any shared paths like .buildkite/.

Sparse checkout and submodule interaction

Submodules are automatically disabled when sparse checkout is enabled. If a step needs both sparse checkout and submodule content, split the work into two steps: one with sparse checkout for the main repository paths, and another without sparse checkout that initializes the required submodules.

Submodules not initializing

If submodules are not being fetched, check:

  • Whether sparse checkout is enabled, as it disables submodule initialization.
  • Whether the agent was started with the --no-git-submodules flag, which forces BUILDKITE_GIT_SUBMODULES=false regardless of pipeline configuration.

SSH key issues during checkout

If the checkout fails with an SSH authentication error, verify that:

  • The secret name meets the naming constraints (starts with a letter, contains only letters, numbers, and underscores, and does not start with buildkite or bk).
  • The secret exists in Buildkite Secrets and contains a valid SSH private key.
  • The ssh_secret key is set at the step level, not the pipeline level.

Checkout still runs despite checkout.skip

If checkout continues to run after setting checkout.skip: true, check the following common causes:

  • YAML indentation error: The skip key must be nested under checkout at the correct level. If skip is not indented as a child of checkout, the agent ignores it.
  • Conflicting environment variable: A hook or plugin may be unsetting BUILDKITE_SKIP_CHECKOUT after the pipeline sets it. Check pre-checkout and environment hooks for variable overrides.
  • Agent version: The native checkout.skip key requires Buildkite agent v3.130.0 or later. Run buildkite-agent --version to confirm.
  • Step-level override: A step-level checkout.skip: false overrides a pipeline-level checkout.skip: true. Check the step definition for an explicit override. See Precedence for the full priority order.

Shallow clone limitations

If a Git command returns unexpected results, check whether the step is using a shallow clone. Operations like git log across the full history, git blame, and git merge-base require complete history. Remove or omit checkout.depth for steps that need full history access.