Skip to content

Latest commit

 

History

History
1205 lines (938 loc) · 52.2 KB

usage.md

File metadata and controls

1205 lines (938 loc) · 52.2 KB

Usage

GitHub Release

This document describes how to use actionlint.

actionlint command

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.

actionlint

When paths to YAML workflow files are given as arguments, actionlint checks them.

actionlint path/to/workflow1.yaml path/to/workflow2.yaml

Explicit 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.yml

When 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.

Commands and compatibility

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 version

check 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 --json

Normal 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=always

Put 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 --help

Do 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.

Output and machine-readable metadata

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.

Configuration inspection

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 validate

Configuration 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.

Shell completion

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/actionlint
mkdir -p ~/.config/fish/completions
actionlint completion fish > ~/.config/fish/completions/actionlint.fish

The 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 $PROFILE

To load the script into the current session only, pipe it through Invoke-Expression instead.

actionlint completion powershell | Out-String | Invoke-Expression

Ignore some errors

Every 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 only

Local 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-action

Reusable 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 error messages

-format option can flexibly format error messages with Go template syntax.

Before explaining the formatting details, let's see some examples.

Example: Legacy template JSON

actionlint -format '{{json .}}'

This returns the original template-field array. Use check --json for the versioned native result described above.

Example: Markdown

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 jsonl

Output:

{"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:

annotations on GitHub Actions

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.sarif

Native SARIF output is a typed JSON document.

Outputs are also too large to be written here. Please read the output example in test data.

Formatting syntax

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.

Exit status

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

Use actionlint on GitHub Actions

Start with the Action guide: quick start, configuration, saved reports, optional PR reviews, tools and the complete input/output reference.

Manual installation

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: bash

Or 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: bash

The 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: -color

Online playground

Thanks 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 Version

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 -version

To 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 -color

To 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.yml

The 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 -color

The 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.

Using actionlint from Go program

Go APIs are available. See the Go API document for more details.

Tools integration

reviewdog

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: any

This 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.

Problem Matchers

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: bash

When you change your workflow and the changed line causes a new error, CI will annotate the diff with the extracted error message.

annotation by Problem Matchers

super-linter

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.

Native Git hooks

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-commit

actionlint 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-commit

The 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 false

Set 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.

pre-commit

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: actionlint

The 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.

Updating pre-commit hooks

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-commit

Only 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.

VS Code

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.

Emacs

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.

Vim and Neovim

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.

Pulsar Edit

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.

Nova

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.

trunk

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 actionlint

or if you'd like a specific version:

trunk check enable actionlint@2.0.0

or modify .trunk/trunk.yaml in your repository to contain:

lint:
  enabled:
    - actionlint@2.0.0

Then just run:

trunk check

and 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

Editor, fixes and remote resolution

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: error

The 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.