actionlint¶
actionlint — a static checker for GitHub Actions workflow files (
.github/workflows/*.yml). It goes far beyond YAML syntax: it type-checks${{ }}expressions, validatesuses:action refs, runner labels,cronand glob patterns, and (its killer feature) pipes eachrun:block through ShellCheck and Python snippets through pyflakes. In this repo it ships both as an on-PATH CLI and as a pre-commit hook.
What it checks¶
| Category | Examples |
|---|---|
| Workflow schema | unknown keys, wrong types, missing required fields |
${{ }} expressions |
undefined context (github.evnt), type mismatches, bad function calls |
uses: refs |
malformed action refs, local action input/output mismatches |
| Runners / cron / globs | invalid runs-on labels, bad schedule.cron, paths/branches globs |
run: shell scripts |
delegates to ShellCheck — quoting, unset vars, SC2086, etc. |
| Embedded Python | run: steps with shell: python → pyflakes |
| Security | script-injection patterns (untrusted ${{ }} interpolated into a shell body) |
How this repo installs it¶
| OS | Mechanism | Owner |
|---|---|---|
| macOS | brew install actionlint (Homebrew-core formula) |
devtools role, alongside shellcheck / shfmt |
| Linux (Debian/Ubuntu) | Linuxbrew best-effort — installed only when brew is present, mirroring how shfmt is handled (actionlint is not in apt) |
same devtools role, # --- shellcheck + shfmt --- section |
Linux gap by design: on a Linux host without Linuxbrew, the CLI is not installed — but the pre-commit hook (below) still lints workflows there, since the actionlint hook variant can build its own binary via Go. See the A–Z row in this_repo/tool-managers.md.
ShellCheck integration (no config needed)¶
actionlint auto-discovers shellcheck on PATH and runs it against every run: block automatically — there is nothing to wire up. This repo already installs shellcheck everywhere (apt on Linux, brew on macOS; see the shellcheck / shfmt row in tool-managers.md), so the integration is active out of the box. If shellcheck were missing, actionlint would simply skip that check rather than error.
pre-commit hook¶
Wired in .pre-commit-config.yaml, grouped with the other linters after shfmt:
- repo: https://github.com/rhysd/actionlint
rev: v1.7.12
hooks:
- id: actionlint-system
files: '^\.github/workflows/.*\.ya?ml$'
actionlint-systemreuses the on-PATH binary thedevtoolsrole installs, so the hook and ad-hocactionlintruns stay on the same version (no second copy, no Go build).files:scopes the hook to the workflow directory — it never fires on unrelated YAML.- The
rhysd/actionlintrepo also offersid: actionlint(language: golang, builds its own binary — use this on hosts without the CLI) andid: actionlint-docker(runs in a container). Both auto-discovershellcheckthe same way.
Run it manually:
Ad-hoc usage¶
actionlint # lint every .github/workflows/*.yml
actionlint .github/workflows/docs.yml # lint one file
actionlint -color # force colored output
actionlint -shellcheck= # disable the shellcheck integration
actionlint -version
Optional config: .actionlintrc.yaml¶
Drop a .actionlintrc.yaml at the repo root to tune behavior — e.g. self-hosted runner labels, per-path ignores, or passing extra flags to shellcheck/pyflakes:
self-hosted-runner:
labels:
- my-custom-runner
config-variables: null # allow any configuration variable name
This repo does not ship one today (defaults are fine for its single docs.yml workflow).
Related¶
- pre-commit — the hook framework this rides on.
tool-managers.md§ Tool index — install mechanism per OS (shared withshellcheck/shfmt).- Upstream: rhysd/actionlint · checks documentation.