actionlint - static checker for GitHub Actions workflow files
actionlint [flags] [--]
[files... | -]
actionlint check [flags] [--]
[files... | -]
actionlint checks GitHub Actions workflow files
without executing the workflows. This is the maintained
kjanat/actionlint fork, distributed as the Go module
actionlint.kjanat.dev/v2.
Checks include workflow syntax, expression types and context
availability, action inputs and outputs, local action metadata and
composite steps, reusable workflow inputs and permissions, job
dependencies, parallel steps, runner labels, event filters, schedules,
and YAML anchors. Security checks report potentially unsafe expression
interpolation in scripts and hard-coded credentials. Optional ShellCheck
integration check scripts in run: steps.
Repository policy checks can require immutable action references, job timeouts, and particular actions. These checks are enabled explicitly in the configuration file. Cache safety policies are enabled by default; ordinary workflow checks do not require configuration.
With no paths, actionlint and actionlint
check find the current repository containing both
.git and .github/workflows, then recursively
check workflow YAML files in that directory. Explicit file arguments and
a lone - for stdin remain supported. Passing a
directory does not enable recursive input expansion; action manifests
are also discovered and can be passed as explicit inputs.
$ actionlint
$ actionlint file1.yaml file2.yaml
$ actionlint check file1.yaml --output-format=json
$ actionlint check --stdin-filename .github/workflows/ci.yml -
The root and check accept options before or after
filenames. -- forces subsequent arguments to be
filenames. An existing regular file named like a command takes
precedence on the root. The hidden --command NAME escape
explicitly selects a command in that case.
actionlint --help shows common options and commands. Run actionlint check --help for all checking options, which the root invocation also accepts.
owner/repo@tag and forge-qualified names. Save its exact
version, URL, publisher identity and SHA256 in the adjacent
actionlint.plugins.lock.json. Commit config and lock.
Without a name, install declared dependencies, preserving matching pins;
migrate older lock-only projects. The default selector for new
dependencies is the highest stable full semantic version; prereleases
and arbitrary tags require exact selection. Admission does not prove
runtime compatibility. Runner targets still require explicit selection.
.github/actionlint.yaml with its YAML Language
Server directive. Refuse to overwrite either supported config filename.
path and config. With --origin,
include sources keyed by JSON Pointer: default or
config, missing/null/value state, and positions for file
settings. Explicit false and empty lists remain distinguishable from
defaults and null.
file:// links when hyperlinks are enabled. Link targets
support Windows drive and UNC paths and encode special characters.
Missing optional tools are reported; malformed selected config returns
status 3.
These options belong to the root and to check, unless noted otherwise. Root parsing retains the original single- and double-dash spellings; check uses long options and short aliases. Run --help-legacy for old spellings.
text, oneline, json,
jsonl, sarif, or github. The
default is text. --output is a supported
alias. JSON emits a versioned analysis result with completion status,
diagnostics, and configuration context. JSONL emits one diagnostic per
line. GitHub annotations are emitted only when explicitly selected.
--json-pretty=false do not select JSON. Explicit true also
indents JSON in pipes and files without color codes or a jq dependency.
Without an explicit pretty flag, pipes and files keep their original
output. JSONL, templates and stderr records keep their existing format.
{{json .}}. Cannot be combined with a built-in output
selector. A nonempty template takes precedence over the legacy
--oneline option.
- means stdout. An
existing destination prompts Overwrite? [y/N] in an
interactive terminal before analysis. Enter or a negative answer keeps
the file and stops the check. Replacement happens only after analysis
completes, including analysis that found problems. A failure preserves a
previous report. The output cannot also be a selected input.
- supplies
workflow stdin. Confirmation never consumes workflow input. Piping
yes also works in an interactive terminal.
auto,
always, or never; a bare flag means
always. On the root, retain the boolean syntax:
-color=false does not force color on. Help uses the same
controls: automatic styling follows stderr's terminal status,
NO_COLOR, and TERM=dumb. With
GITHUB_ACTIONS=true, help and text diagnostics also use
color in workflow logs without a terminal, even with
TERM=dumb. Regular files and
--output-file reports stay plain in auto mode; custom
templates receive no added color. Put root color flags before
--help. Redirected structured output contains no
terminal color codes. Optional jq formatting can color JSON and SARIF
written directly to a terminal.
auto (default), always, or
never for OSC 8 links in help and doctor.
always and never override the environment. In
auto, non-empty NO_HYPERLINKS disables
links before non-empty FORCE_HYPERLINKS enables them;
otherwise the output must be a TTY with a known capable terminal (stderr
for help, stdout for doctor). Unknown terminals and multiplexers default
to plain URLs. Explicit always can enable links through a
multiplexer configured to pass them through. Color controls are
independent. Destination URLs stay visible when links are disabled.
JSON, diagnostics, templates, version output and generated completion
scripts receive no added hyperlink sequences. Put root flags before
--help. See https://no-hyperlinks.org/spec for the
convention.
kjanat:actionlint/analyzer@0.1.0 or
kjanat:actionlint/runner-provider@0.1.0. Repeat to load
several. Each analysis runs in a fresh instance with 64 MiB of memory
and 10 seconds; a trap, an exit or an exceeded limit fails that
document's analysis.
https://plugins.actionlint.kjanat.dev/.
github;
installing a provider does not change the target. Also available on
lsp.
content,
scope, obtained_at, freshness
(fresh, stale, or missing) and
complete. Maximum 8 MiB. The content is provider-specific.
Restart the language server to refresh evidence. Also available on
lsp.
<stdin>. Use an existing workflow path to
select its repository context.
none, info, or debug. The
default is none.
ACTIONLINT_STRICT.
ACTIONLINT_EXPERIMENTAL. May be
combined with --strict.
summary field.
Default: true. Use --summary=false or
--quiet to suppress it.
shellcheck on PATH. Use --shellcheck=COMMAND
for a custom executable or command line. Also available on
doctor. Legacy -shellcheck COMMAND remains
supported on the root.
F) rules when no Ruff option is supplied. An empty command
or false disables Python linting. The command value is
accepted for compatibility and is never executed.
ruff on
PATH. Use --ruff=COMMAND for a custom executable or command
line. Ruff must already be installed. Also available on
doctor. Legacy -ruff COMMAND remains
supported on the root. Uses isolated, read-only checks with Ruff's
default rule selection. tools.ruff configures rule
selection and the Python target version.
tools.<name>.backend.process
reports an error before the executable is launched. Explicit CLI and
environment tool selections authorize their own executable.
The older --ruff=false, --shellcheck=false,
and empty command values remain supported. A disable flag takes
precedence when both forms are supplied.
bash, fish,
powershell, zsh, pwsh, shell
executable paths, and auto. Cobra generates all four
completion scripts. Install bash-completion for Bash and run compinit
for Zsh. Regenerate saved scripts after upgrading.
latest as described above.
Configuration is optional. In a detected repository, actionlint reads
the first of .github/actionlint.yaml,
.github/actionlint.yml,
.github/actionlint.json and
.github/actionlint.toml that exists.
--config-file selects a different file; settings are
not merged with the repository file or a user-global configuration.
The available settings are:
path.Match glob syntax.
vars context. Omitted or
null disables this check; an empty list allows no
configuration variables.
null disables this check. The built-in secrets
GITHUB_TOKEN, ACTIONS_STEP_DEBUG, and
ACTIONS_RUNNER_DEBUG, and secrets declared in
on.workflow_call.secrets, remain allowed even with an empty
list.
permissions.
Defaults to restricted, which assumes read access to
contents and packages. permissive
assumes write access; id-token still requires an explicit
grant. This setting does not change the repository's actual GitHub
permissions.
/ separators and support ** and brace
alternatives. Each entry's ignore list contains message
regular expressions, applied in addition to --ignore.
true, require action and reusable workflow references
to use 40- or 64-digit hexadecimal commit IDs, and
docker:// images to use digests. Local references and
expression-based references are skipped.
true, require timeout-minutes on jobs
that run steps. A mapping such as
{ min-minutes: 5, max-minutes: 60 } sets inclusive bounds
for literal timeout values. Either bound can be omitted; each must be
finite and positive, and the minimum cannot exceed the maximum. Jobs
calling reusable workflows are skipped; expression-based timeouts are
not compared.
true or { scope: workflow }, require a
workflow-level permissions declaration. Use
{ scope: job } to require one on every job, including
reusable workflow calls, even when the workflow declares permissions.
Empty mappings, named scopes, read-all, and
write-all satisfy the check. This policy checks declaration
presence; it does not infer least privilege.
actions/checkout or
my-org/security-scan@v2*. Only steps directly in the
workflow are searched. Workflows consisting entirely of reusable
workflow calls, or containing an expression-based action reference, are
skipped.
false to disable this check.
false to disable
this check.
false to
disable this check.
true, or select a nonempty rules list in a
mapping. report selects all (default),
suppression, or violation. Omission,
null, or false permits inline exceptions.
The three cache policies accept line-scoped exceptions with an exact rule name and a reason:
cache-mode: write # actionlint:ignore cache-write-untrusted -- jobs use reviewed default-branch code only
Alternatively, place
# actionlint:ignore-next-line RULE -- REASON immediately
before the reported declaration. These directives do not suppress other
rules. CLI and configured ignore patterns still apply afterwards. See
the configuration document for the full trigger list, cache action
modes, nested-call behavior, and suppression controls.
Other policy checks are off until enabled. For example:
self-hosted-runner: { labels: [linux.2xlarge] }
config-variables: [DEFAULT_RUNNER]
config-secrets: [DEPLOY_TOKEN]
assume-default-permissions: restricted
policy:
require-commit-hash: true
require-job-timeout: { min-minutes: 5, max-minutes: 60 }
require-permissions: true
required-actions: [actions/checkout]
paths:
".github/workflows/**/*.{yml,yaml}":
ignore: ['shellcheck reported issue in this script: SC2086:.+']
The repository supplies actionlint.schema.json for
editor completion and validation. See the configuration document below
for the schema URL and full matching rules.
ShellCheck checks supported shell scripts in run: steps.
The executables must be available through PATH or the
corresponding command flag. If a command cannot be resolved, its
integration is skipped; --verbose explains why.
$ actionlint --shellcheck=
$ actionlint --shellcheck='shellcheck -e SC2086'
Command strings are parsed into an executable and arguments, not executed by a shell. Shell pipes and redirections are not supported in these flags. Your arguments precede actionlint's own arguments, so do not supply input filenames or override ShellCheck's output format.
actionlint uses JSON1 output and disables rc-file discovery with
--norc by default. Set tools.shellcheck.config
to inline settings, an rc-file path, or a directory containing
.shellcheckrc or shellcheckrc. An rc path
replaces --norc with --rcfile; relative config
paths resolve from the selected actionlint configuration file's
directory. Additional options can use --shellcheck
arguments or SHELLCHECK_OPTS.
ShellCheck runs for recognized bash, sh,
dash, and ksh interpreters or a supported
dialect selected by a leading # shellcheck shell=...
directive. Unresolved explicit shell expressions, unknown wrappers, and
non-shell scripts are otherwise skipped. Global dialect settings and
command flags alone do not select them. If no shell is configured and
the runner platform is unknown, actionlint assumes Bash.
Diagnostics go to standard output. Command usage, progress logs, and fatal errors go to standard error. The default diagnostic includes the file, line, column, message, rule name, and source snippet. --oneline omits the snippet; --format replaces the diagnostic presentation.
The Go template receives a sequence of errors. Each has
Message, Snippet, Kind,
Filepath, Line, Column, and
EndColumn fields. Line and column numbers start at 1.
Custom template functions include json,
replace, toPascalCase, allKinds,
and getVersion.
JSON:
$ actionlint --json
JSON Lines:
$ actionlint --output jsonl
Custom lines:
$ actionlint --format '{{range .}}{{.Filepath}}:{{.Line}}:{{.Column}}: {{.Message}} [{{.Kind}}]\n{{end}}'
Backslash escapes such as \n in the format string are
expanded before the template is parsed.
SARIF uses the bundled template and needs no separate template file:
$ actionlint --output sarif > actionlint.sarif
The CLI's --format accepts template text. The GitHub
Action's format input instead accepts names such as
json and sarif. Changing output format does
not change the lint exit status.
Native JSON returns a versioned analysis result shared with the
GitHub Action. Each diagnostic contains rule,
message, path, start,
end, and an optional source-line snippet.
Positions use one-based Unicode character columns and exclusive end
positions; a range may end on a later line. A clean single-file check
includes these fields:
{"schema_version":1,"status":"success","completed":true,"exit_code":0,"file_count":1,"diagnostics":[],"hints":[]}
The result also includes configurations with selected
settings and their origins. See Analysis
results for the schema, types, and compatibility rules. JSON Lines
writes one diagnostic per line with schema_version: 1 on
every record, or nothing for a clean check. The legacy
{{json .}} template retains its array, field names, and
inclusive end_column contract.
In JSON modes, incomplete checks emit the result on standard error
with completed: false, an error, and a nonzero
exit_code. An empty diagnostics array alone
does not indicate success. Argument errors before operation selection
contain only error and exit_code. Requested
progress or debug logs are separate JSON Lines records with a
log field on standard error. --quiet
suppresses these log records. Standard output contains only the
requested result. An early failure can leave standard output empty;
always check the exit status.
Machine-readable metadata:
$ actionlint --help --json
$ actionlint --version --json
$ actionlint --init-config --json
JSON help describes flag names, shorthand, types, default spellings,
groups, choices, repeatability, legacy aliases, and exit codes. JSON
version includes name, version,
installed_from, go_version, os,
and goarch. JSON config creation returns
{"path":"..."}. The default three-line version format
remains unchanged.
--completion generates native completion scripts for
Bash, Fish, Zsh, and PowerShell. Cobra generates these scripts from the
CLI flag definitions. Scripts complete long flags, short options, flag
values, and workflow paths. Bash requires the
bash-completion package; Zsh requires
compinit. Load one into the current shell session:
Bash:
$ source <(actionlint --completion bash)
Fish:
$ actionlint --completion fish | source
Zsh, after initializing its completion system:
$ autoload -Uz compinit && compinit
$ source <(actionlint --completion zsh)
PowerShell:
actionlint --completion powershell | Out-String | Invoke-Expression
For persistent installation, save Bash output in a directory loaded
by bash-completion, Fish output as
~/.config/fish/completions/actionlint.fish, or Zsh output
as _actionlint in a directory on fpath. Load
PowerShell output from your profile. Regenerate saved scripts after
upgrading actionlint so they reflect the installed CLI. See the usage
document for setup examples.
ACTIONLINT_OUTPUT_FORMAT, ACTIONLINT_JSON, ACTIONLINT_TEMPLATE, ACTIONLINT_TEMPLATE_FILE, ACTIONLINT_OUTPUT_FILE : Output defaults. Explicit format flags replace environment format selection. Templates and output files apply only to checks.
See Environment variables for precedence, defaults and examples.
0 or false,
disables automatic color; an empty value has no effect.
--color can force color; --no-color
always disables it. See the NO_COLOR
convention.
true enables automatic color for help and text
diagnostics in workflow logs. NO_COLOR and explicit
color controls still apply. This does not enable hyperlinks.
auto mode, a nonempty NO_HYPERLINKS
disables help and doctor links. Otherwise, a nonempty
FORCE_HYPERLINKS enables them even in redirected
output. An empty value has no effect; 0 counts as nonempty.
Explicit hyperlink modes override both variables. These controls are
independent of color.
SHELL takes precedence over the PowerShell fallback
indicated by a nonempty PSModulePath.
Detailed documentation for this release and current installation options are available online.
https://github.com/kjanat/actionlint/blob/v2.0.0/docs/checks.md
Full list of all checks done by actionlint with example inputs, outputs, and playground links.
https://github.com/kjanat/actionlint/blob/master/docs/install.md
Installation instructions for npm, Homebrew, AUR, Scoop, aqua, mise, release archives, the download script, Docker, and Go, plus the status of WinGet and upstream-only package names.
https://github.com/kjanat/actionlint/blob/v2.0.0/docs/usage.md
CLI usage, shell completion, output templates, the GitHub Action, Docker images, and editor and CI integrations.
https://github.com/kjanat/actionlint/blob/v2.0.0/docs/config.md
Repository configuration, runner labels, variables, secrets, and policy checks.
https://github.com/kjanat/actionlint/blob/v2.0.0/docs/api.md
How to use actionlint as Go library.
https://github.com/kjanat/actionlint/blob/v2.0.0/docs/reference.md
Links to resources.
The repository provides a GitHub Action with actionlint, and ShellCheck in a prebuilt Docker image. It reports GitHub annotations by default and fails when problems are found:
name: Lint GitHub Actions workflows
on: [push, pull_request]
permissions: { contents: read }
jobs:
actionlint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
with: { persist-credentials: false }
- uses: kjanat/actionlint@v1
The Docker action requires a Linux runner with a reachable Docker daemon. To run the binary directly, including on macOS, Windows with Bash, or a runner without a Docker daemon, use the download script:
- name: Check workflow files
run: |
bash <(curl -fsSL https://raw.githubusercontent.com/kjanat/actionlint/662318dd6bbd9c0c120e35b03168bc1be69bf428/scripts/download-actionlint.bash) latest
./actionlint --color
shell: bash
The script accepts latest to resolve the newest release,
or a specific version. Without a version argument, it uses the default
recorded in the script. It writes the executable to the current
directory and, on GitHub Actions, exposes its path as the
executable step output. External linters must be available
separately when using the binary.
actionlint command exits with one of the following exit
statuses.
The WebAssembly playground runs actionlint in your browser. Workflow linting happens locally in the browser; it does not execute workflows or run the external ShellCheck programs.
https://kjanat.github.io/actionlint/
Paste a workflow into the editor to see diagnostics update as you type. Select a diagnostic to jump to its source position.
Report problems with this fork to its issue tracker. Include the output of --version, the relevant configuration, and a minimal workflow that reproduces the problem.
https://github.com/kjanat/actionlint/issues
actionlint is licensed under the MIT License.
Copyright (c) 2026 Kaj Kowalski
Copyright (c) 2021 rhysd
https://github.com/kjanat/actionlint/blob/HEAD/LICENSE.txt