For the complete documentation index, see llms.txt.

Run checks before you push with Chainguard Workspaces

Run your repository's checks against uncommitted changes in a Chainguard Workspaces session, and validate your Chainguard Checks configuration offline before you push.
  7 min read

Waiting for a pull request’s checks to find a mistake is slow, especially for a coding agent that has to push, wait, and read the results before it can try again. With Chainguard Workspaces, you can run your repository’s checks against your working tree, uncommitted changes included, before you push. This page explains how, and how to find mistakes in your configuration without running anything. To run checks on a pushed commit without a pull request, or to read results and your checks’ history from your terminal, refer to Inspect and run checks with chainctl.

Run checks in a Workspaces session

Every Workspaces session has a run-ci command that runs the checks your repository defines against the working tree in /work. It checks only /work, so it can’t check a git worktree elsewhere in the session. To run every check, run run-ci with no arguments in the session:

run-ci

The command prints each step’s output as it runs, followed by a result line. For a repository with vet and test checks, the output looks similar to the following:

run-ci: checks vet, test (advisory: the gate runs the verdict on push)
── vet[0]: go vet ./...
── vet[0]: exit 0 (9.281s)
── test[0]: go test ./...
ok      github.com/acme/app    0.004s
── test[0]: exit 0 (3.843s)
PASS in 13.131s

Each ── line names a check and the number of the step within it, counting from 0, followed by the step’s command or its exit code and duration. The first line is a reminder that the pull request’s run, not run-ci, decides what GitHub shows. When a check declares a different environment from the session’s, run-ci also prints a note about it.

To run a single check, pass its name:

run-ci test

The run-ci command exits with one of the following codes, so scripts and agents can act on the result:

Exit codeMeaning
0Every check passed.
1A check failed. The last line names the check and the number of the step that failed.
2The checks couldn’t run. The last line says why.

How run-ci differs from a pull request run

The run-ci command reads your checks from the session’s working tree, so it uses your latest edits to the configuration too. The following table shows how it differs from a pull request run:

What differsrun-ci in a sessionA pull request run
ConfigurationThe session’s working tree, including uncommitted edits.The pull request’s test merge commit, with the base branch’s rules applied.
Which checks runEvery check that has commands, in the order your configuration lists them. It stops at the first check that fails, including an advisory check.The checks that the run’s plan selects, each after the checks it needs. An advisory check never blocks merging.
needs, when, trigger, and timeoutIgnored, so run-ci also runs checks that a pull request would skip.Applied.
foreachRuns the check once for each variation, shown in the output as <check>/<variation>, such as test/api.Runs each variation in its own VM, and reports all of them as one GitHub check.
Checks that need a workload identity, services in the check’s environment, or inputsSkipped, with a line for each, such as run-ci: skipped deploy: declares identity gcp-prod, which only CI provides. A pass from run-ci doesn’t cover them.Run.
UserThe session’s user, which is root unless the session’s environment comes from a check with a different user.Each check’s user, which defaults to root.
EnvironmentThe session as it is. Packages you installed in the session are available, and the check’s own package list and network access don’t apply.A fresh VM for each check, with the check’s packages and network access.
Size and CPU architectureThe session’s. A session runs on x86-64 unless its resource class ends in -arm64, as described in Resources.Each check’s resources. A class without a suffix usually runs on arm64.

If you name a skipped check, as in run-ci deploy, or if run-ci skips every check, it exits with 2. A session whose environment comes from a check with a different user, such as a session you open with chainctl develop --check, runs as that user. It runs only the checks with that user, and skips the rest the same way. In a root session, every check runs as root.

Because of these differences, a pass from run-ci predicts that the pull request’s checks will pass, but only the pull request’s run in fresh VMs decides what GitHub shows.

The run-ci command works while something is attached to the session, such as your terminal. It also works after you detach, if you keep the session running with a Detach — keep running option or --keep, as described in Keep a session running while you’re away. Running checks doesn’t keep a session from parking, so if the session parks while run-ci runs, the run stops and run-ci exits with 2.

Only one run-ci or chainctl develop check can run in a session at a time. One that starts while another runs fails and tells you to wait for the first to finish.

Run checks in a session from your laptop

To run a session’s checks without attaching to it, use chainctl develop check on your laptop with the session’s name or short ID. If the session is parked, chainctl resumes it first. Replace $SESSION with the session’s name or short ID, and $ORGANIZATION with the name of your Chainguard organization:

chainctl develop check $SESSION --parent $ORGANIZATION

To run a single check, add --check with its name:

chainctl develop check $SESSION --check test --parent $ORGANIZATION

The command uses the same runner as run-ci and streams each step’s output. It skips the same checks as run-ci, and prints a line for each, such as Skipped deploy: declares identity gcp-prod, which only CI provides. It exits with 0 when the checks pass, or 1 when a check fails or the checks can’t run, including when you name a skipped check with --check. To print the result as JSON, add --output json. The JSON lists the checks it skipped under skipped.

If you detached from the session and kept it running, chainctl develop check might fail with a message that says the guest is held by another controller. Run the command again, or attach to the session and run run-ci in it.

Validate your configuration offline

To find mistakes in your configuration without running anything, run chainctl checks validate anywhere in your repository’s checkout:

chainctl checks validate

With no argument, the command finds your repository’s root. You can also pass the path of a repository or of a configuration file. For a valid configuration, the command prints each workflow it found:

workflow ci (.chainguard/ci.yaml)
ok: the document lowers

For a configuration with problems, it prints each problem with its file, line, and column, and exits with a non-zero status. For example, a string cmd with a pipe in it produces an error that suggests using a shell:

.chainguard/ci.yaml:6:10: check t: cmd "go test ./... | tee out": unquoted shell metacharacter '|': a string `cmd:` is safe-split with no shell behind it (no pipes, redirects, globs, or expansion) — quote the character, or write `cmd: [sh, -c, "..."]` to opt into a shell explicitly

Because validate needs no network access or Chainguard account, you can run it in a git pre-commit hook.

To see how Chainguard Checks interprets your configuration, run chainctl checks explain. It prints each check with its environment, trigger, and commands, plus its conditions, network access, and timeout when they’re set:

chainctl checks explain

Run explain when a check doesn’t behave as you expect, such as running when you thought a condition would skip it.

For a pull request, Chainguard Checks reads only the files directly in your repository’s root .chainguard/ directory and the files under .chainguard/ci/. In the directory layout, validate and explain read the same files. In the single-file layout, they also read .chainguard/ directories below the root, such as testdata/.chainguard/, and folders inside .chainguard/. As a result, they can list a workflow that pull requests never run, or fail on a file that pull requests ignore. To avoid this, move those files out of .chainguard/ directories, or use the directory layout.

Last updated: 2026-10-20 15:06