This document describes how to use actionlint.
With no argument, actionlint checks workflows and action.yml/action.yaml
manifests in the current repository, including actions that no workflow references.
Repositories containing only actions are supported too.
actionlintWhen paths to YAML workflow files are given as arguments, actionlint checks them.
actionlint path/to/workflow1.yaml path/to/workflow2.yamlExplicit action.yml and action.yaml inputs are checked as action metadata,
including composite steps and their ShellCheck and Ruff findings. You can mix
workflows and actions in one invocation:
actionlint .github/workflows/ci.yml .github/actions/build/action.ymlWhen a selected workflow references a selected action, both use the supplied
action content. Shared metadata defects are reported once at their declaration;
caller-specific input errors still point to the workflow's uses: step.
Files directly under .github/workflows remain workflows, even when named
action.yml or action.yaml.
Metadata findings point into the action file. Configuration overrides, rule levels
and ignores apply to that file. Discovery skips .git, node_modules, vendor,
.venv, venv, .cache, .worktrees and testdata directories, nested Git repositories,
and symlinked action files or directories. Inside a Git repository, discovery
asks git 2.8 or newer which paths its ignore rules exclude: .gitignore,
.git/info/exclude and the global excludes file apply, and tracked manifests in
ignored directories stay discovered. Discovery fails when git is missing or
reports an incomplete listing. files.excludes remains the explicit override.
Use file exclusions to
omit fixtures or generated actions. Explicit paths can select actions in skipped
directories; configured file exclusions still apply.
Standalone actions have no known runner, so executable-bit is checked only when
an action is reached through a workflow with a known runner. The Go API's
OnRulesCreated hook applies to workflow inputs only; standalone metadata uses
the built-in metadata and script checks, without invoking custom workflow rules.
When - argument is given, actionlint reads inputs from stdin and checks it as
workflow source.
cat path/to/workflow.yaml | actionlint -Use actionlint --help for common options and commands, and
actionlint check --help for all checking options. The root invocation also
accepts the options listed under check. See the online command manual
for the full reference.
The root invocation and the explicit check command accept options before or
after filenames. Legacy single-dash names such as -format are accepted at the
root and after subcommands, alongside --format. Boolean
options accept =false. Both routes use the same analysis engine. Use -- to
treat every remaining argument as a filename, including names beginning with -.
actionlint
actionlint workflow.yml
actionlint check workflow.yml --output-format=json
actionlint check --stdin-filename=.github/workflows/ci.yml -
actionlint config show --origin
actionlint rules expression
actionlint doctor
actionlint versioncheck accepts workflow files, action manifests or a lone - for stdin. With no
paths, it uses the same repository discovery as the root. Directory
arguments are not supported. Use --stdin-filename=action.yml to check action
metadata from stdin.
An existing regular file named check, config, rules, doctor, completion,
version or help takes precedence over the corresponding root command. Use --
to force filenames or --command NAME to force a command despite a matching file.
Root options still belong before filenames:
actionlint -- --workflow.yml
actionlint --command check workflow.yml --jsonNormal help and completion recommend the modern names. --help-legacy lists the
supported old names without deprecation warnings. --format remains an alias for
Go templates; it never interprets template text as a built-in format name.
-o, -f, -q and -h mean output format, template, quiet and help.
-v remains available for progress logging; new examples use --log-level=info.
-V is the short form of --version and preserves its three-line output.
Attached short-option values such as -ojson belong to check's modern grammar.
The root's -color=false means not forcing color on. If both -color and
-no-color are true, -no-color wins regardless of order. The modern command
accepts check --color=auto|always|never; bare --color means always.
Human-readable help styles headings, command names and flags when stderr is a
terminal. With GITHUB_ACTIONS=true, help and text diagnostics also use color in
workflow logs without a terminal, including when TERM=dumb. Redirects to regular
files and --output-file reports stay plain in auto mode. Custom templates receive
no added color. JSON can use optional terminal formatting described below.
Following the NO_COLOR convention,
any non-empty NO_COLOR value, including 0 or false, disables automatic color;
an empty value has no effect. TERM=dumb disables automatic help styling outside GitHub Actions.
An explicit color request overrides those environment settings.
--no-color and check --color=never disable styling. Root color flags belong
before --help, since root help exits immediately. Redirected JSON help is uncolored.
Help can make the project name and documentation URLs clickable using OSC 8
terminal hyperlinks. The documentation URL uses the release tag or the build's
source commit. Without embedded version or VCS metadata, it falls back to HEAD;
use go run -buildvcs=true ./cmd/actionlint --help to include the checkout revision
when running from source.
Doctor uses file:// links for its directory, configuration
file and resolved executables. Select --hyperlinks=auto|always|never independently of
color. The default auto follows the no-hyperlinks convention:
non-empty NO_HYPERLINKS disables links, then non-empty FORCE_HYPERLINKS
enables them, then the output stream's TTY status and known terminal capabilities decide:
stderr for help, stdout for doctor.
Even 0 is a non-empty value. Explicit always or never overrides both variables.
Unknown terminals and multiplexers use plain URLs in auto mode; always can
enable links when the terminal and multiplexer are configured to pass them through.
Both modes keep destination URLs visible. JSON, diagnostics, templates, version
output and generated completion scripts remain free of added hyperlink sequences.
actionlint --hyperlinks=always --help
actionlint check --hyperlinks=never --help
actionlint doctor --hyperlinks=alwaysPut root hyperlink flags before --help, just like color flags. The same modes
are available in subcommand help and generated shell completions.
When running through npm, these forms pass the help flag to actionlint:
npx @kjanat/actionlint --help
npm exec --package=@kjanat/actionlint -- actionlint --helpDo not put an extra -- after the package in the first form. It reaches actionlint
as the end of options, making the following --help a filename.
actionlint check --json
actionlint check --output-format=jsonl
actionlint check --output-format=sarif --output-file=actionlint.sarif
actionlint check --output-format=github
actionlint check --template-file=report.tmpl
actionlint check --help --json
actionlint version --json--output-format accepts text, oneline, json, jsonl, sarif and github.
--json selects JSON. --template and its supported --format alias render the
existing Go-template fields and functions. --template-file reads the same syntax
from a file. A template cannot be combined with a built-in output selector.
The legacy -oneline option still yields to a nonempty template.
JSON returns a versioned document. Each diagnostic has rule, message, path,
start, end and an optional snippet. Positions use one-based Unicode character
columns, and the end position is exclusive. Ranges may span multiple lines. For example:
{
"schema_version": 1,
"status": "problems-found",
"completed": true,
"exit_code": 1,
"file_count": 1,
"configurations": [],
"hints": [],
"diagnostics": [
{
"rule": "expression",
"message": "undefined variable \"missing\"",
"path": ".github/workflows/ci.yml",
"start": { "line": 6, "column": 24 },
"end": { "line": 6, "column": 36 }
}
]
}CLI and Action share one result contract, published as a JSON Schema
and TypeScript types in the npm package. A clean JSON result has an empty diagnostics array. JSON Lines emits those
individual diagnostic objects with schema_version: 1 on every record, or nothing for a clean result. Legacy
-format '{{json .}}' retains its original array and field names, including
kind, filepath and end_column. SARIF uses the bundled renderer. github
emits escaped workflow annotation commands; it is enabled only by that explicit
format choice, including when running inside GitHub Actions.
Check results go to stdout, or to --output-file PATH. --output-file - means stdout.
If the destination exists, an interactive terminal asks Overwrite? [y/N]
before analysis. Enter or a negative answer keeps the existing file and stops the
check. Use -y or --yes to answer yes without prompting. This is required when
stderr is not a terminal or - supplies workflow input; confirmation never consumes
workflow stdin. yes | actionlint --output-file report.json also works in an
interactive terminal. The destination option applies only to checks.
A report file is replaced only after analysis completes; an operational failure
preserves any previous report. An output file cannot replace a consumed workflow, local action, reusable workflow, configuration, template, or existing stdin filename, including links to those files. Replacing a report preserves its permission bits; a new report is private to the current user.
Logs and operational errors go to stderr. Structured output never includes ANSI
color or terminal hyperlinks. Checks print a summary to stderr by default.
Try additional checks without changing a configuration file:
actionlint --strict
actionlint --experimental
actionlint check --strict --experimental--strict selects all stable catalog rules, including non-recommended suspicious checks
and opt-in repository policies. Explicit group and individual rule exceptions remain in effect, as do
configured timeout bounds, permission scopes, suppression report modes and
rule-ID/path ignores. It does not invent a
required-actions list or install missing external linters. --experimental
enables nursery rules while retaining individual exclusions. Strict
mode alone does not enable nursery rules. Both switches apply after file
overrides; --experimental=false clears only nursery selections. No current rules are in nursery.
--strict=false removes the strict preset. Policies enabled in configuration remain active.
Use ACTIONLINT_STRICT=true and ACTIONLINT_EXPERIMENTAL=true for equivalent
environment defaults; explicit flags, including false, take precedence.
--log-level=none|info|debug controls logging. Checks print workflow and finding
counts on stderr by default. --summary=false disables the summary.
--quiet suppresses logs and summaries while preserving requested results
and errors. In JSON modes, check failures on stderr use the same result contract, with
completed: false, error, and exit_code. Metadata commands keep their own
error records. Log
records have log, and the summary has summary.files and summary.findings.
Check the exit status before interpreting empty stdout as success.
| Exit status | Meaning |
|---|---|
0 |
Completed without findings, or completed an information/configuration command |
1 |
Completed with findings |
2 |
Invalid CLI arguments or incompatible options |
3 |
Could not initialize or complete, including invalid config, regex or template |
--help --json describes commands, flags, choices, defaults and exit codes.
version --json exposes build metadata. The old -version and --version flags
retain their original three-line text output.
When stdout is a terminal and jq is installed, JSON metadata and JSON/SARIF
diagnostics are indented and colored according to the color controls. Use
--json-pretty=false to disable this. Without an explicit pretty flag, pipes and
files retain their existing output. Missing or failing jq falls back to the
original JSON. It is never installed automatically.
Explicit --json-pretty also selects JSON when no other output format is set.
For example, actionlint rules --json-pretty prints the rules as JSON. The default
value and --json-pretty=false do not change the selected output format.
Explicit --json-pretty also indents JSON written to pipes and files, using
built-in formatting without color codes or a jq dependency. JSONL, templates and
stderr records retain their existing output.
CLI defaults can also come from environment variables, including
configuration selection, output, logging and external linters. Explicit flags
take precedence, including empty strings and false.
actionlint check --config=other.yml workflow.yml
actionlint check --no-config workflow.yml
actionlint config init
actionlint config path
actionlint config show --origin
actionlint config validateConfiguration selection remains one explicit file or the repository's
.github/actionlint.yaml, .github/actionlint.yml, .github/actionlint.json, then
.github/actionlint.toml. There is no new global
configuration search or merge policy. ACTIONLINT_CONFIG and
ACTIONLINT_NO_CONFIG provide selection defaults; explicit config flags override
both. --no-config skips
configuration loading. An explicit check --config also avoids parsing an
unselected repository config.
config path prints the selected path, or nothing when none is selected. It can
identify an invalid file without parsing it. config show prints effective YAML;
--json returns a document with path and config. --origin adds an origins
map keyed by JSON Pointer. Sources distinguish default from config, and
states distinguish missing keys, explicit null and explicit values such as
false and []. Config sources include line and column positions. A default
value does not imply that the user explicitly set it.
config validate uses the same parser and validation as analysis, including its
current handling of unknown keys described in Configuration. It does
not silently introduce the stricter JSON Schema validation policy.
config init retains the repository destination, refuses to overwrite either
config filename, and includes the YAML Language Server schema directive.
It creates a commented configuration reference with lint rules and tools first.
init, init-config, --init and --init-config are compatibility aliases;
config --init and config --init-config also work. The success message displays
a path relative to the working directory, linked to the file when terminal
hyperlinks are enabled. JSON output retains the absolute path.
rules lists checks by name, description and category; rules NAME explains one
check. doctor reports the selected config and resolves configured external-tool
commands without executing them. Its paths become clickable file:// links when
hyperlinks are enabled, with spaces and non-ASCII characters encoded in the target.
Windows drive and UNC paths are supported. Missing optional tools are reported as unavailable;
a malformed selected configuration returns exit status 3. Help, version and
completion do not load configuration or create a linter. Rules load selected plugin
declarations without running analysis.
completion <shell> prints a completion script for the given shell to stdout.
completions command is an alias; either command without a shell shows help.
Help examples follow the same shell detection as completion auto: a supported
SHELL wins, then the presence of PSModulePath selects PowerShell. When neither
signal resolves, help lists explicit shell choices without shell-specific loading syntax.
Executable suffixes follow PATHEXT, matched case-insensitively. When it is unset
or empty, the Windows fallback is .COM;.EXE;.BAT;.CMD.
Supported shells are bash, fish, powershell and zsh. The value also
accepts pwsh as an alias for powershell and executable paths such as $SHELL.
auto selects the shell from $SHELL, then tries PowerShell when $PSModulePath
is set. Cobra generates the scripts from the CLI definitions. They complete long flags,
short options, values, and workflow paths. Install bash-completion for Bash,
and initialize compinit for Zsh. Regenerate saved scripts after upgrading.
mkdir -p ~/.local/share/bash-completion/completions
actionlint completion bash > ~/.local/share/bash-completion/completions/actionlintmkdir -p ~/.config/fish/completions
actionlint completion fish > ~/.config/fish/completions/actionlint.fishThe zsh script belongs in a directory listed in $fpath.
actionlint completion zsh > "${fpath[1]}/_actionlint"The PowerShell script is loaded from your profile. The first command creates
the profile's directory, which does not exist on a fresh account and makes
Out-File fail.
New-Item -ItemType Directory -Force (Split-Path -Parent $PROFILE) | Out-Null
actionlint completion powershell | Out-File -Append -Encoding utf8 $PROFILETo load the script into the current session only, pipe it through
Invoke-Expression instead.
actionlint completion powershell | Out-String | Invoke-ExpressionEvery registered diagnostic rule supports inline exceptions
with a rule name and an optional reason. Place the comment on the reported line, or use
actionlint:ignore-next-line immediately before it. A directive on a YAML block scalar header covers its body.
cache-mode: write # actionlint:ignore cache-write-untrusted -- this job runs reviewed default-branch code onlyLocal actions must have an action.yml or action.yaml available when linting.
For generated actions, map the runtime path to its source
to validate the checked-in manifest. For intentional exceptions, use a short comment:
- uses: ./generated-action # actionlint:ignore missing-local-actionReusable ignores let you declare an exception once for matching files, jobs, steps or action references.
To ignore some errors, -ignore option offers to filter errors by messages
using regular expression. The option is repeatable. The regular expression
syntax is the same as RE2.
actionlint -ignore 'label ".+" is unknown' -ignore '".+" is potentially untrusted'The -shellcheck option accepts a command name, path, or command line with arguments.
An empty value disables ShellCheck.
actionlint -shellcheck=
actionlint --shellcheck='shellcheck -e SC2086'Ruff checks Python run: steps when ruff is installed on PATH. A bare
--ruff selects ruff on PATH. Use --ruff=COMMAND for a custom executable or
command line, and --no-ruff to disable it. ShellCheck follows the same pattern:
--shellcheck, --shellcheck=COMMAND, and --no-shellcheck.
Optional command values require = so the next argument remains a workflow path.
A disable flag wins if both forms are supplied and skips the tool's environment
settings. Legacy single-dash spellings follow the same rules at the root and after
subcommands. The older =false and empty values also remain supported.
No Python interpreter or Pyflakes installation is needed. See Ruff configuration.
Repository configuration can select native executables through
tools.<name>.backend.process. Authorize these selections with
--allow-config-processes only when you trust the complete configuration,
including inherited files and overrides. Explicit CLI and environment commands
already authorize their own executable.
The deprecated -pyflakes COMMAND option selects Ruff's Pyflakes (F) rules
when no Ruff option is supplied. The command value is never executed.
An empty value or false disables Python linting. Explicit Ruff options take precedence.
To configure executables, arguments and child environments separately, use
ACTIONLINT_SHELLCHECK_BIN, ACTIONLINT_SHELLCHECK_FLAGS, and
ACTIONLINT_SHELLCHECK_ENV.
Ruff has matching ACTIONLINT_RUFF_BIN, ACTIONLINT_RUFF_FLAGS, and
ACTIONLINT_RUFF_ENV settings.
An explicit tool flag overrides all three environment settings for that tool.
See External linter environment settings for quoting,
Windows paths and examples.
ShellCheck runs for recognized shell interpreters or scripts selected by a leading
native shell directive. Configured dialects and command flags alone do not select
unknown or non-shell scripts.
ShellCheck commands, including wrappers, run in the step's resolved working
directory so sourced files resolve as they do on the runner. Arguments are passed
literally; use absolute paths for wrapper-owned files. For ShellCheck rc files,
use tools.shellcheck.config, which resolves paths
relative to the actionlint configuration file.
Your arguments are prepended to actionlint's integration arguments. actionlint
owns the JSON1 output format and stdin input, so do not pass -f/--format or
file arguments. It supplies its default exclusions, rc-file selection and source
following settings. An inferred --shell <dialect> is added only when no explicit
ShellCheck flag or leading shell directive selects the dialect. ShellCheck
arguments take precedence over SHELLCHECK_OPTS, followed by a leading shell
directive, actionlint's configured dialect and the workflow-derived dialect. See
script selection and directives.
By default, --norc disables rc discovery. Set
tools.shellcheck.config to an inline mapping or an rc
file/directory; an rc path replaces --norc with --rcfile. Following sourced
files (-x) can be disabled in
configuration and is disabled when the step's working directory cannot be resolved
locally. Additional options can use -shellcheck '<command line>' or the
SHELLCHECK_OPTS environment variable.
-format option can flexibly format error messages with Go template syntax.
Before explaining the formatting details, let's see some examples.
actionlint -format '{{json .}}'This returns the original template-field array. Use check --json for the
versioned native result described above.
actionlint -format '
{{range $err := .}}### Error at line {{$err.Line}}, col {{$err.Column}} of `{{$err.Filepath}}`
{{$err.Message}}
```plaintext
{{$err.Snippet}}
```
{{end}}
'Output:
### Error at line 21, col 20 of `test.yaml`
property "platform" is not defined in object type {os: string}
```plaintext
key: ${{ matrix.platform }}-node-${{ hashFiles('**/package-lock.json') }}
^~~~~~~~~~~~~~~
```Example: Serialized in JSON Lines
actionlint --output jsonlOutput:
{"message":"unexpected key \"branch\" for ...
{"message":"character '\\' is invalid for branch ...
{"message":"label \"linux-latest\" is unknown. ...
Example: Error annotation on GitHub Actions
actionlint -format '
{{range $err := .}}::error file={{$err.Filepath}},line={{$err.Line}},col={{$err.Column}}::{{$err.Message}}%0A```%0A{{replace $err.Snippet "\\n" "%0A"}}%0A```
{{end}}
' -ignore 'SC2016:'Output:
To include newlines in the annotation body, it prints %0A. (ref
actions/toolkit#193). And it
suppresses SC2016 shellcheck rule error since it complains about the template
argument.
Basically it is more recommended to use Problem Matchers or reviewdog as explained in 'Tools integration' section below.
Example: SARIF format
The Static Analysis Results Interchange Format (SARIF) is a standardized format for the results of static analysis tools.
Use the built-in format directly:
actionlint --output sarif > actionlint.sarifNative SARIF output is a typed JSON document.
Outputs are also too large to be written here. Please read the output example in test data.
In Go template syntax, . within {{ }} means the target object. Here, the target object is a sequence of error
objects.
The sequence can be traversed with range action, which is like for ... = range ... {} in Go.
{{range $err := .}} this part iterates error objects with the iteration variable $err {{end}}
The error object has the following fields.
| Field | Description | Example |
|---|---|---|
{{$err.Message}} |
Body of error message | property "platform" is not defined in object type {os: string} |
{{$err.Snippet}} |
Code snippet to indicate error position | node_version: 16.x\n ^~~~~~~~~~~~~ |
{{$err.Kind}} |
Name of rule the error belongs to | expression |
{{$err.Filepath}} |
Canonical relative file path of the error position | .github/workflows/ci.yml |
{{$err.Line}} |
Line number of the error position (1-based) | 9 |
{{$err.Column}} |
Column number of the error's start position (1-based) | 11 |
{{$err.EndColumn}} |
Column number of the error's end position (1-based) | 23 |
Functions called in {{ }} placeholder are template actions. There are many
actions defined by Go standard library. In addition, there are a few custom
actions defined by actionlint. Most useful action would be json as we already
used it in the above JSON example. List of all custom actions are as follows:
| Action | Description | Example usage |
|---|---|---|
json x |
Serialize x as JSON string followed by newline character |
{{json $err}} |
replace x y z |
Replace string y with z in x |
{{replace $err.Filepath "\\" "/"}} |
toPascalCase x |
Convert x into PascalCase (e.g. 'foo-bar' to 'FooBar') |
{{toPascalCase $err.Kind}} |
allKinds |
Return an array of kind objects. The kind object is explained in the below table | {{range $ = allKinds}}{{$.Name}}{{end}} |
getVersion |
Return the version of actionlint as string | {{getVersion}} |
The kind object returned from allKinds action has the following fields.
| Field | Description | Example |
|---|---|---|
{{$kind.Name}} |
Name of the kind | syntax-check |
{{$kind.Description}} |
Short description of the kind | Checks for GitHub Actions workflow syntax |
For example, the following simple iteration body
line is {{$err.Line}}, col is {{$err.Column}}, message is {{$err.Message | printf "%q"}}
will produce output like below.
line is 21, col is 20, message is "property \"platform\" is not defined in object type {os: string}"
In {{ }} placeholder, input can be piped and action can be used to transform
texts. In above example, the message is piped with | and transformed with
printf "%q".
Note that special characters escaped with backslash like \n in the format
string are automatically unescaped.
actionlint command exits with one of the following exit statuses.
| Status | Description |
|---|---|
0 |
The command ran successfully and no problem was found |
1 |
The command ran successfully and some problem was found |
2 |
The command failed due to invalid command line option |
3 |
The command failed due to some fatal error |
Start with the Action guide: quick start, configuration, saved reports, optional PR reviews, tools and the complete input/output reference.
The download script remains useful when direct access to the executable is
preferred. It sets an absolute file path of the downloaded
executable to the executable output for following steps.
Here is an example of simple workflow to run actionlint on GitHub Actions.
Please ensure shell: bash since the default shell for Windows runners is
pwsh.
name: Lint GitHub Actions workflows
on: [push, pull_request]
jobs:
actionlint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
with: { persist-credentials: false }
- name: Download actionlint
id: get_actionlint
run: bash <(curl -fsSL https://raw.githubusercontent.com/kjanat/actionlint/662318dd6bbd9c0c120e35b03168bc1be69bf428/scripts/download-actionlint.bash) 2.0.0
shell: bash
- name: Check workflow files
env: { actionlint: "${{ steps.get_actionlint.outputs.executable }}" }
run: "${actionlint}" -color
shell: bashOr simply download the executable and run it in one step:
- name: Check workflow files
run: |
bash <(curl -fsSL https://raw.githubusercontent.com/kjanat/actionlint/662318dd6bbd9c0c120e35b03168bc1be69bf428/scripts/download-actionlint.bash) 2.0.0
./actionlint -color
shell: bashThe download script allows to specify the version of actionlint and the download
directory. Try to give --help argument to the script for more usage details.
If you want to enable
shellcheck integration, install shellcheck
command. Note that shellcheck is
pre-installed on Ubuntu worker.
If you want to annotate errors from actionlint on GitHub, consider using Problem Matchers.
If you prefer Docker image to running a downloaded executable, using actionlint Docker image is another option.
name: Lint GitHub Actions workflows
on: [push, pull_request]
jobs:
actionlint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
with: { persist-credentials: false }
- name: Check workflow files
uses: docker://ghcr.io/kjanat/actionlint:latest
with:
args: -colorThanks to WebAssembly, actionlint playground is available on your browser. It never sends any data to outside your browser.
https://kjanat.github.io/actionlint
Paste your workflow content to the code editor at left pane. It automatically shows the results at right pane. When editing the workflow content in the code editor, the results will be updated on the fly. Clicking an error message in the results table moves a cursor to position of the error in the code editor.
Docker image
Docker image is available. Images built from this revision contain actionlint,
ShellCheck, and the native Ruff executable. No Python interpreter is required.
Ruff's official release archive is pinned by version and SHA-256 for both Linux amd64 and arm64.
Previously published images are unchanged; Ruff will be included in the next release image.
Available tags are:
ghcr.io/kjanat/actionlint:latest:
Moving alias for the latest stable version of actionlint. This image is recommended.ghcr.io/kjanat/actionlint:{version}:
Release-specific actionlint image rather than a moving alias.
(e.g.ghcr.io/kjanat/actionlint:2.0.0)
The CLI image is also published to Docker Hub as kjanat/actionlint:latest and
kjanat/actionlint:{version}. Both registries carry the same manifest, so pick
whichever your setup pulls from more easily. Previous action-* images remain
available for older action refs; new releases use the JavaScript action.
The ordinary binary also supports -github-action to run the action adapter
with INPUT_*, GITHUB_WORKSPACE, and GITHUB_OUTPUT environment variables.
For byte-for-byte reproducibility, use the image's manifest digest as
ghcr.io/kjanat/actionlint:{version}@sha256:<digest>.
Just run the image with docker run:
docker run --rm ghcr.io/kjanat/actionlint:latest -versionTo check all workflows in your repository, mount your repository at the image's
default working directory, /w:
docker run --rm -v "$(git rev-parse --show-toplevel):/w" ghcr.io/kjanat/actionlint:latest -colorTo check a file with actionlint in a Docker container, pass the file content via
stdin and use - argument:
cat /path/to/workflow.yml | docker run --rm -i ghcr.io/kjanat/actionlint:latest -color -Or mount the workflows directory and pass the paths as arguments:
docker run --rm -v /path/to/workflows:/workflows ghcr.io/kjanat/actionlint:latest -color /workflows/ci.ymlThe container inherits its environment from docker run, so SHELLCHECK_OPTS
reaches shellcheck inside the image only when you pass it in with -e:
docker run --rm -v "$(git rev-parse --show-toplevel):/w" \
-e SHELLCHECK_OPTS='-e SC2086' ghcr.io/kjanat/actionlint:latest -colorThe action-* images are the exception. Their shellcheck input is a boolean that switches the integration on or off, so
SHELLCHECK_OPTS in the step's env: is the only way to configure shellcheck
there.
Go APIs are available. See the Go API document for more details.
reviewdog posts inline review comments and filters findings to changed
lines, keeping feedback focused on the pull request.
The reviewdog/action-actionlint action runs this fork
with ShellCheck enabled.
We recommend v1.76.2 or newer.
Add the following workflow to .github/workflows/reviewdog-actionlint.yaml:
name: reviewdog-actionlint
on: [pull_request]
jobs:
actionlint:
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: write
steps:
- uses: actions/checkout@v7
with: { persist-credentials: false }
- uses: reviewdog/action-actionlint@v1
with:
reporter: github-pr-review
filter_mode: added
fail_level: anyThis posts review comments for findings on added or modified lines and fails the
step when those lines have problems. Set filter_mode: diff_context to include
the surrounding diff context, or fail_level: none to leave comments without
failing the step. Invalid options and fatal errors always fail the step.
For pull requests from forks, reviewdog falls back to GitHub annotations when the workflow token cannot post review comments.
Pass additional actionlint options through actionlint_flags. See the
action documentation for all inputs and available reporters.
Note
The example below downloads this fork and installs this fork's problem matcher, so annotations include fork-specific checks.
Problem Matchers is a feature to extract GitHub Actions annotations from terminal outputs of linters.
Copy actionlint-matcher.json to
.github/actionlint-matcher.json in your repository.
Then enable the matcher using add-matcher command before running actionlint
in the step of your workflow.
- name: Check workflow files
run: |
echo "::add-matcher::.github/actionlint-matcher.json"
bash <(curl -fsSL https://raw.githubusercontent.com/kjanat/actionlint/662318dd6bbd9c0c120e35b03168bc1be69bf428/scripts/download-actionlint.bash) 2.0.0
./actionlint -color
shell: bashWhen you change your workflow and the changed line causes a new error, CI will annotate the diff with the extracted error message.
Warning
super-linter copies the binary from the rhysd/actionlint container image
into its own image. Its GitHub Actions linter therefore runs upstream, not
this fork.
super-linter is a Bash script for a simple combination of various linters, provided by GitHub. It has support for actionlint. Running super-linter in your repository automatically runs actionlint.
To ignore some errors, please add -ignore option by using
GITHUB_ACTIONS_COMMAND_ARGS environment variable.
Please see
super-linter/super-linter#1852
for the discussion.
Git 2.54 and newer let you register named hooks directly in Git
configuration. To run actionlint before each commit, install actionlint on your PATH, then run
these commands in your repository:
git config set --local hook.actionlint.command 'actionlint pre-commit'
git config set --local hook.actionlint.event pre-commitactionlint pre-commit ignores the arguments Git passes to a hook, so the same command also works for events
such as pre-push.
Git runs actionlint when you commit. A lint failure blocks the commit. This checks discovered workflows and actions in the working tree, including unstaged edits; it does not check an isolated copy of the staged files.
List the registered hooks and try them before committing:
git hook list --show-scope pre-commit
git hook run pre-commitThe settings are local to this checkout and are not copied when someone clones the repository. Git runs the
installed binary; you manage its installation and updates yourself. An existing .git/hooks/pre-commit
script also runs, after the configured hooks.
To disable only the actionlint hook, keeping its configuration:
git config set --local hook.actionlint.enabled falseSet the same key to true to enable it again.
Git 2.55 adds parallel execution for eligible hook events and
event-wide controls. The pre-commit event always runs its hooks sequentially.
For a discussion of native hooks and the pre-commit framework, see does git 2.54 make pre-commit obsolete? by anthonywritescode.
For v2, pre-commit hooks are published separately in kjanat/actionlint-pre-commit. The first v2 hook release is pending; replace the placeholder below with a published tag from that repository before installing:
repos:
- repo: https://github.com/kjanat/actionlint-pre-commit
rev: <published-v2-tag>
hooks:
- id: actionlintThe default actionlint hook installs an exact actionlint-kjanat wheel dependency
in pre-commit's Python environment. The wheel contains the native host binary; no Go
compiler or separate native ShellCheck/Ruff installer is needed. The project's
plugin declarations and lockfile select and pin Wasm analyzers.
Initialize or migrate those files with actionlint before committing them.
The distribution also offers actionlint-system for an existing installation and
actionlint-docker for its pinned published container. See the hook repository for
platform support and release prerequisites.
All hooks run actionlint pre-commit on every commit and check all repository
workflows, including when only a local action, reusable workflow, or configuration
is changed or deleted. They use always_run: true and pass_filenames: false.
The native command succeeds when the repository has no workflows and otherwise
performs ordinary repository analysis. Only referenced local actions receive
metadata validation; missing actions report missing-local-action.
Change the repo: URL explicitly when migrating from v1: pre-commit autoupdate
does not move a configuration to another repository. Old immutable v1 hook revisions
remain available at the old URL. See v2 migration.
After the first v2 hook release, update within the dedicated repository:
pre-commit autoupdate --freeze --repo https://github.com/kjanat/actionlint-pre-commitOnly completed stable hook releases receive tags in that repository; unrelated
source, Action-channel, and development tags remain in the host repository.
Review the selected release and commit the updated revision. A frozen revision pins
the hook installation; actionlint.plugins.lock.json separately pins the project's plugins.
Note
The extension runs the command configured in linter-actionlint.config,
which defaults to actionlint on PATH. It uses this fork when that command
resolves to a fork build; it does not download actionlint itself.
Linter extension for VS Code is available. The
extension automatically detects .github/workflows directory, runs actionlint
command, and reports errors in the code editor while editing workflow files.
Note
Both plugins run a locally installed executable named actionlint by
default. Flycheck exposes flycheck-actionlint-executable, and Flymake
exposes flymake-actionlint-executable, so either can select this fork
explicitly.
Plugins for both Flycheck and Flymake are available via MELPA.
Their respective repositories are flycheck-actionlint and flymake-actionlint.
Note
Both integrations run a local actionlint executable. nvim-lint's linter
command can be overridden, and ALE exposes
g:ale_yaml_actionlint_executable, so either can select this fork.
nvim-lint supports actionlint on Neovim. The plugin automatically and asynchronously runs actionlint and notifies errors on the fly when you edit GitHub Actions CI workflows. Please read the plugin's documentation for more details.
ALE supports actionlint on Vim and Neovim. Similar to nvim-lint, The plugin automatically and asynchronously runs actionlint and notifies errors on the fly when you edit GitHub Actions CI workflows. Please read the plugin's documentation for more details.
Note
The package runs the local executable configured by actionsExecutablePath,
which defaults to actionlint on PATH. Point that setting at this fork's
binary to use fork-specific checks.
A Linter package for Pulsar Edit is available. The
package automatically detects a workflows directory, executes the actionlint
command on any detected GitHub Actions files within the directory, and reports
returned information in the code editor display tab while editing workflow
files.
Note
The extension runs the local executable configured by
actionlint.binarypath, which defaults to actionlint on PATH. Point that
setting at this fork's binary to use fork-specific checks.
Nova.app is a MacOS only editor and IDE. The Actionlint for Nova allows you to get inline feedback while editing actions.
Warning
trunk's actionlint plugin downloads releases from rhysd/actionlint using
hard-coded upstream URLs. The trunk check enable actionlint commands below
install upstream, not this fork.
trunk is an extendable superlinter with a builtin language server and preexisting issue detection. Actionlint is integrated in trunk-io/plugins.
Once you have initialized trunk in your repo, to enable at the latest actionlint version, just run:
trunk check enable actionlintor if you'd like a specific version:
trunk check enable actionlint@2.0.0or modify .trunk/trunk.yaml in your repository to contain:
lint:
enabled:
- actionlint@2.0.0Then just run:
trunk checkand it will check your modified files via actionlint, if applicable, and show you the results. Trunk also will detect preexisting issues and highlight only the newly added actionlint issues. For more information, check the trunk docs.
You can also see actionlint issues inline in VS Code via the Trunk VS Code extension.
Checks | Installation | Configuration | Go API | References
actionlint lsp serves LSP over stdin/stdout. It supports UTF-16 positions,
versioned open buffers, ranged changes, diagnostic publication, and versioned
quick-fix edits. Open document changes invalidate analysis of open dependants.
The client applies edits; the server never writes editor buffers to disk.
actionlint fix [files...] previews source edits as JSON. --apply writes the
validated edits and checks the files again. Only fixes marked safe are selected
by default; --include-suggested also includes edits requiring review. Conflicting
edits or stale source revisions abort preparation. Writes are atomic per file;
an I/O failure during a multi-file application reports how many files were written.
actionlint plugin search discovers published plugins. plugin install owner/repo@TAG
adds a dependency to the config's root plugins list and pins its verified artifact
in the adjacent actionlint.plugins.lock.json; commit both files.
Check, fix, rules and LSP load declared plugins using their pins. plugin install
without a name resolves manually edited declarations or migrates older lock-only projects.
plugin sync restores exact pins; plugin update explicitly refreshes declared versions.
See plugin installation
for version selection, overrides and offline behavior.
--plugin PATH accepts document-analyzer components, runner providers or the Ruff core WebAssembly
engine. The Ruff engine replaces the native Ruff process while retaining the
host's workflow expression masking and YAML position mapping.
Remote dependency resolution is opt-in: --resolve-with EXECUTABLE selects a
provider, and repeatable --resolver-arg VALUE supplies literal arguments. The
commitish provider is maintained separately as actionlint-plugin-commitish. Build it
from that project with cargo build --locked --release. Its executable accepts --token-env NAME only when
you explicitly choose a credential variable. It resolves action and reusable
workflow references using their respective commitish semantics, and proposes
reviewable commit pins. Provider errors fail analysis instead of implying absence.
The default local analysis makes no resolution requests.
The language server provides expression property completions, including
github.event payload fields selected by the workflow's triggers and activity
filters. Dot access, quoted bracket access, array indices and wildcard access
use the same types as expression diagnostics. Completions use the current editor
buffer, including unsaved changes. Unknown events and caller-provided payloads
remain open; conditions do not yet narrow the event type within a branch.
Expression findings have independent rule settings. For example, allowing unknown payload properties does not disable script-injection checks:
lint:
rules:
correctness:
event-payload: off
expression-reference: error
security:
script-injection: errorThe former expression catch-all has been split into expression-syntax,
expression-reference, context-availability, expression-type,
expression-call, event-payload and script-injection. Update existing
suppressions and severity settings to the specific check. The expression rule
now covers expression values and interpolation forms required by workflow fields.
See the rule reference for each rule's configuration group and help.

