Skip to content
Latchkey

actionlint: Lint GitHub Actions Workflows

actionlint validates GitHub Actions workflow files, catching bad syntax, undefined expression contexts, and ShellCheck issues in run steps before they fail a real run.

Workflow bugs are slow to find because you have to push to test them. actionlint checks the YAML locally and in CI in a fraction of a second.

What it does

actionlint scans .github/workflows automatically (or the files you name), validating workflow schema, ${{ }} expression types, runner labels, action input names, and shell inside run: via ShellCheck. It exits 1 when it finds problems, 0 when clean.

Common usage

Terminal
# auto-discover and lint all workflows
actionlint
# lint a specific file with a chosen format
actionlint -format '{{json .}}' .github/workflows/ci.yml
# run via the official Docker image
docker run --rm -v "$PWD":/repo -w /repo rhysd/actionlint

Flags

FlagWhat it does
(no args)Lint every workflow under .github/workflows
-format <tmpl>Go-template output (e.g. JSON for tooling)
-shellcheck <path>Path to shellcheck (empty string disables it)
-pyflakes <path>Path to pyflakes for python run steps
-ignore <regex>Suppress errors matching a pattern
-colorForce colored output

In CI

actionlint needs shellcheck on PATH to lint run-step shell; the official Docker image bundles it. To skip shellcheck (for example on a minimal runner), pass -shellcheck= with an empty value rather than letting it error.

Common errors in CI

"label \"ubuntu-latest-8core\" is unknown" means a self-hosted or custom runner label actionlint does not know; declare it in actionlint.yaml. "property \"foo\" is not defined in object type" flags a typo in a context like steps or env. "could not parse as YAML" means the workflow file itself is malformed.

Using this in CI

  • Set the shell explicitly and enable set -euo pipefail; the default runner shell does not stop on the first error inside a multi-line block.
  • Pin the tool version. An unpinned build tool turns a runner image update into a build break on unchanged code.
  • Prefer non-interactive and batch flags. Progress output designed for a terminal bloats CI logs and can hang without a TTY.
  • Write machine-readable output (JSON, JUnit) to a file and upload it as an artifact, so a failure is diagnosable without re-running the job.

Frequently asked questions

actionlint: Lint GitHub Actions Workflows?
Workflow bugs are slow to find because you have to push to test them. actionlint checks the YAML locally and in CI in a fraction of a second.
What it does?
actionlint scans .github/workflows automatically (or the files you name), validating workflow schema, ${{ }} expression types, runner labels, action input names, and shell inside run: via ShellCheck. It exits 1 when it finds problems, 0 when clean.
In CI?
actionlint needs shellcheck on PATH to lint run-step shell; the official Docker image bundles it. To skip shellcheck (for example on a minimal runner), pass -shellcheck= with an empty value rather than letting it error.
Common errors in CI?
"label \"ubuntu-latest-8core\" is unknown" means a self-hosted or custom runner label actionlint does not know; declare it in actionlint.yaml. "property \"foo\" is not defined in object type" flags a typo in a context like steps or env. "could not parse as YAML" means the workflow file itself is malformed.

Related guides

References

Run this faster and cheaper on Latchkey managed runners - self-healing included. Start free → 30-day trial · No credit card