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.
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:
-
Run pre-checkout hooks: Agent and plugin
pre-checkouthooks run before checkout starts. -
Run the checkout: Unless a custom
checkouthook 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 cleanto 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.
-
Run post-checkout hooks: Agent, repository, and plugin
post-checkouthooks 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.
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:
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:
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:
-
BUILDKITE_SKIP_CHECKOUTexported by anenvironmentorpre-checkouthook (runs last, so it can override any earlier value) - Step-level
checkout.skipin pipeline YAML - Pipeline-level
checkout.skipin pipeline YAML -
BUILDKITE_SKIP_CHECKOUTset in a pipeline or stepenvblock - Agent
--skip-checkoutCLI flag - 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:
steps:
- label: "Notify deploy"
command: "curl -X POST https://example.com/webhook"
env:
BUILDKITE_SKIP_CHECKOUT: "true"
After — native checkout.skip key:
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.
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.
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.
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):
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:
steps:
- label: "Pipeline upload"
command: "buildkite-agent pipeline upload"
plugins:
- sparse-checkout#v1.7.0:
paths:
- .buildkite
After — native checkout.sparse key:
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-coneflag. The native feature uses cone mode only. -
clean_checkout: The plugin can remove lock files, reset the repository, and clean untracked files before checkout. Usecheckout.flags.cleanto customize clean flags natively, or handle cleanup in apre-checkouthook. -
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 thessh-keyscanstep. 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. Omitcheckout.depthto 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.
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.
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:
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:
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:
-
clone: SetsBUILDKITE_GIT_CLONE_FLAGS -
fetch: SetsBUILDKITE_GIT_FETCH_FLAGS -
checkout: SetsBUILDKITE_GIT_CHECKOUT_FLAGS -
clean: SetsBUILDKITE_GIT_CLEAN_FLAGS
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.
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.
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:
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:
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:
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:
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:
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:
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:
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
buildkiteorbk
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
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:
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 --versionon the agent to confirm. -
Cone mode restrictions: The native
checkout.sparsefeature 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 theno_coneoption instead. -
Step-level override: A step-level
sparse.pathsreplaces 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-submodulesflag, which forcesBUILDKITE_GIT_SUBMODULES=falseregardless 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
buildkiteorbk). - The secret exists in Buildkite Secrets and contains a valid SSH private key.
- The
ssh_secretkey 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
skipkey must be nested undercheckoutat the correct level. Ifskipis not indented as a child ofcheckout, the agent ignores it. -
Conflicting environment variable: A hook or plugin may be unsetting
BUILDKITE_SKIP_CHECKOUTafter the pipeline sets it. Checkpre-checkoutandenvironmenthooks for variable overrides. -
Agent version: The native
checkout.skipkey requires Buildkite agent v3.130.0 or later. Runbuildkite-agent --versionto confirm. -
Step-level override: A step-level
checkout.skip: falseoverrides a pipeline-levelcheckout.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.