:octocat: Static checker for GitHub Actions workflow files
macos-14
macos-14-xlarge
macos-14-large
ubuntu-18.04
runner label from runners list since it is no longer supported. (#363)self-hosted-runner.labels
configuration. For example, the following configuration defines any runner labels prefixed with private-linux-
. (thanks @kishaningithub, #378)
self-hosted-runner:
labels:
- private-linux-*
-format
option is used for linting multiple workflow files. Thanks @ReinAchten-TomTom for your help on the investigation. (#370)google-github-actions/auth
google-github-actions/get-secretmanager-secrets
google-github-actions/setup-gcloud
google-github-actions/upload-cloud-storage
pulumi/actions
pypa/gh-action-pypi-publish
windows-latest-8-cores
ubuntu-latest-4-cores
ubuntu-latest-8-cores
ubuntu-latest-16-cores
pull_request
event.
enqueued
dequeued
milestoned
demilestoned
SHELLCHECK_OPTS
environment variable to pass arguments to shellcheck. See the shellcheck's official document for more details.
# Enable some optional rules
SHELLCHECK_OPTS='--enable=avoid-nullary-conditions' actionlint
# Disable some rules
SHELLCHECK_OPTS='--exclude=SC2129' actionlint
docker.io
host name in pre-commit hook. (thanks @gotmax23, #382)actionlint.RuleBase.Config
method to get the actionlint configuration passed to rules. (thanks @hugo-syn, #387)actionlint.ContainsExpression
function to check if the given string contains ${{ }}
placeholders or not. (thanks @hugo-syn, #388)x/sys
package.actionlint -format "$(cat /path/to/sarif_template.txt)" > output.json
allKinds
returns the kinds (lint rules) information as an array. You can include what lint rules are defined in the command output.toPascalCase
converts snake case (foo_bar
) or kebab case (foo-bar
) into pascal case (FooBar
).if:
is always evaluated to true. See the check document to know more details. (#272)
# ERROR: All the following `if:` conditions are always evaluated to true
- run: echo 'Commit is pushed'
if: |
${{ github.event_name == 'push' }}
- run: echo 'Commit is pushed'
if: "${{ github.event_name == 'push' }} "
- run: echo 'Commit is pushed to main'
if: ${{ github.event_name == 'push' }} && ${{ github.ref_name == 'main' }}
${{ }}
placeholders in environment variable names. (#312)
env:
"${{ steps.x.outputs.value }}": "..."
${{ }}
(#285)
strategy:
matrix:
test:
# Matrix rows are assigned from JSON string
- ${{ fromJson(inputs.matrix) }}
steps:
- run: echo ${{ matrix.test.foo.bar }}
exclude
of matrix was incorrect when some matrix row is dynamically constructed with ${{ }}
. (#261)
strategy:
matrix:
build-type:
- debug
- ${{ fromJson(inputs.custom-build-type) }}
exclude:
# 'release' is not listed in 'build-type' row, but it should not be reported as error
# since the second row of 'build-type' is dynamically constructed with ${{ }}.
- build-type: release
exclude
of matrix was incorrect when object is nested at row of the matrix. (#249)
matrix:
os:
- name: Ubuntu
matrix: ubuntu
- name: Windows
matrix: windows
arch:
- name: ARM
matrix: arm
- name: Intel
matrix: intel
exclude:
# This should exclude { os: { name: Windows, matrix: windows }, arch: {name: ARM, matrix: arm } }
- os:
matrix: windows
arch:
matrix: arm
actionlint.yml
config file is used by multiple goroutines to check multiple workflow files. (#333)steps:
# ERROR: 'run:' is correct
- ruN: echo "hello"
number
as input type of workflow_dispatch
event. (#316)workflow_dispatch
event is 10.timeout-minutes
and max-parallel
are greater than zero.RuleBase
methods public which are useful to implement your own custom rule type. (thanks @hugo-syn, #327, #331)OnRulesCreated
field is added to LinterOptions
struct. You can modify applied rules with the hook (add your own rule, remove some rule, ...).NewProject()
Go API to create a Project
instance..tar.gz
link. (#307)-ignore
option is RE2. (#320)watch
webhook's types (thanks @suzuki-shunsuke, #334)secret_source
property to github
context. (thanks @asml-mdroogle, #339)actions/checkout@v4
).runs-on:
. Now runs-on:
can have group:
and labels:
configurations. Please read the official document for more details. (#280)
runs-on:
group: ubuntu-runners
labels: ubuntu-20.04-16core
macos-latest-xl
, macos-13-xl
, macos-12-xl
labels are available at runs-on:
. (#299, thanks @woa7)-stdin-filename
command line argument. Even if the workflow content is passed via stdin, actionlint can recognize reusable workflows depended by the workflow using file path passed at -stdin-filename
argument. (#283)watch
webhook.matrix
context) when ${{ }}
is used in the row value. (#294)go install ./...
doesn't work. (#297)actionlint
pre-commit hook to use Go toolchain. Now pre-commit automatically installs actionlint
command so you don't need to install it manually. Note that this hook requires pre-commit v3.0.0 or later. For those who don't have Go toolchain, the previous hook is maintained as actionlint-system
hook. Please read the document to know the usage details. (#301, thanks @Freed-Wu and @dokempf)wasm-opt
.sparse-checkout
input of actions/checkout
action. (#305)config-variables:
- DEFAULT_RUNNER
- DEFAULT_TIMEOUT
inputs
context is shared by multiple events. (#263)vars
context causes 'undefined context' error. This context is for 'Variables' feature which was recently added to GitHub Actions. (#260)
- name: Use variables
run: |
echo "repository variable : ${{ vars.REPOSITORY_VAR }}"
echo "organization variable : ${{ vars.ORGANIZATION_VAR }}"
echo "overridden variable : ${{ vars.OVERRIDE_VAR }}"
echo "variable from shell environment : $env_var"
github
context's properties which were added recently. (#259)set-output
or save-state
and suggest the alternative. See the document for more details. (#234)
# ERROR: This format of 'set-output' workflow command was deprecated
- run: echo '::set-output name=foo::bar'
${{ }}
expression at on.workflow_call.inputs.<id>.default
caused an error. (#235)
on:
workflow_call:
inputs:
project:
type: string
# OK: The default value is generated dynamically
default: ${{ github.event.repository.name }}
inputs
context to grow gradually while checking inputs in workflow_call
event.
on:
workflow_call:
inputs:
input1:
type: string
# ERROR: `input2` is not defined yet
default: ${{ inputs.input2 }}
input2:
type: string
# OK: `input1` was already defined above
default: ${{ inputs.input1 }}
${{ }}
expression is used.
on:
workflow_call:
inputs:
input1:
type: boolean
input2:
type: number
# ERROR: Boolean value cannot be assigned to number
default: ${{ inputs.input1 }}
set-output
format yet. (#240)set-output
workflow command in our own workflows. (#239, thanks @Mrtenz)jobs.<job_id>.env
workflow key does not allow accessing env
context, but jobs.<job_id>.steps.env
allows. See the official document for the complete list of contexts availability. (#180)
...
env:
TOPLEVEL: ...
jobs:
test:
runs-on: ubuntu-latest
env:
# ERROR: 'env' context is not available here
JOB_LEVEL: ${{ env.TOPLEVEL }}
steps:
- env:
# OK: 'env' context is available here
STEP_LEVEL: ${{ env.TOPLEVEL }}
...
actionlint reports the context is not available and what contexts are available as follows:
test.yaml:11:22: context "env" is not allowed here. available contexts are "github", "inputs", "matrix", "needs", "secrets", "strategy". see https://docs.github.com/en/actions/learn-github-actions/contexts#context-availability for more details [expression]
|
11 | JOB_LEVEL: ${{ env.TOPLEVEL }}
| ^~~~~~~~~~~~
success()
or failure()
are only available in conditions of if:
. See the official document for the complete list of special functions availability. (#214)
...
steps:
# ERROR: 'success()' function is not available here
- run: echo 'Success? ${{ success() }}'
# OK: 'success()' function is available here
if: success()
actionlint reports success()
is not available and where the function is available as follows:
test.yaml:8:33: calling function "success" is not allowed here. "success" is only available in "jobs.<job_id>.if", "jobs.<job_id>.steps.if". see https://docs.github.com/en/actions/learn-github-actions/contexts#context-availability for more details [expression]
|
8 | - run: echo 'Success? ${{ success() }}'
| ^~~~~~~~~
inputs
context is not available in run-name:
section. (#223)shell: ${{ env.SHELL }}
.on:
does not exist at toplevel. (#232)on:
section is empty.in_progress
type to workflow_run
webhook event trigger.actions/setup-dotnet@v3
to popular actions data set.generate-availability
script was created to scrape the information about contexts and special functions availability from the official document. The information can be used through actionlint.WorkflowKeyAvailability()
Go API. This script is run once a week on CI to keep the information up-to-date.run-name
which GitHub introduced recently. It is a name of workflow run dynamically configured. See the official document for more details. (#220)
on: push
run-name: Deploy by @${{ github.actor }}
jobs:
...
end_column
property to JSON representation of error. The property indicates a column of the end position of ^~~~~~~
indicator in snippet. Note that end_column
is equal to column
when the indicator cannot be shown. (#219)
$ actionlint -format '{{json .}}' test.yaml | jq
[
{
"message": "property \"unknown_prop\" is not defined in object type {arch: string; debug: string; name: string; os: string; temp: string; tool_cache: string; workspace: string}",
"filepath": "test.yaml",
"line": 7,
"column": 23,
"kind": "expression",
"snippet": " - run: echo ${{ runner.unknown_prop }}\n ^~~~~~~~~~~~~~~~~~~",
"end_column": 41
}
]
permissions:
are exceptionally case-sensitive.
inputs
for workflow_dispatch
were not case-insensitive.actions/stale@v6
was newly added.# .github/workflows/reusable.yaml
on:
workflow_call:
inputs:
INPUT_UPPER:
type: string
input_lower:
type: string
secrets:
SECRET_UPPER:
secret_lower:
...
# .github/workflows/test.yaml
...
jobs:
caller:
uses: ./.github/workflows/reusable.yaml
# Inputs and secrets are case-insensitive. So all the followings should be OK
with:
input_upper: ...
INPUT_LOWER: ...
secrets:
secret_upper: ...
SECRET_LOWER: ...
actionlint
binary with the download script. (#218)./
). (#179).
jobs.<job_id>.with
and jobs.<job_id>.secrets
. See the document for more details.
# .github/workflows/reusable.yml
on:
workflow_call:
inputs:
name:
type: string
required: true
secrets:
password:
required: true
...
# .github/workflows/test.yml
...
jobs:
missing-required:
uses: ./.github/workflows/reusable.yml
with:
# ERROR: Undefined input "user"
user: rhysd
# ERROR: Required input "name" is missing
secrets:
# ERROR: Undefined secret "credentials"
credentials: my-token
# ERROR: Required secret "password" is missing
jobs.<job_id>.with
. Types are defined at on.workflow_call.inputs.<name>.type
in reusable workflow. actionlint checks types of expressions in workflow calls. See the document for more details.
# .github/workflows/reusable.yml
on:
workflow_call:
inputs:
id:
type: number
message:
type: string
...
# .github/workflows/test.yml
...
jobs:
type-checks:
uses: ./.github/workflows/reusable.yml
with:
# ERROR: Cannot assign string value to number input. format() returns string value
id: ${{ format('runner name is {0}', runner.name) }}
# ERROR: Cannot assign null to string input. If you want to pass string "null", use ${{ 'null' }}
message: null
jobs.<job_id>.uses
. See the document for more details.
jobs:
test:
# ERROR: This workflow file does not exist
with: ./.github/workflows/does-not-exist.yml
needs.<job_id>.outputs.<output_id>
in downstream jobs of workflow call jobs. The outputs object is now typed strictly based on on.workflow_call.outputs.<name>
in the called reusable workflow. See the document for more details.
# .github/workflows/get-build-info.yml
on:
workflow_call:
outputs:
version:
value: ...
description: version of software
...
# .github/workflows/test.yml
...
jobs:
# This job's outputs object is typed as {version: string}
get_build_info:
uses: ./.github/workflows/get-build-info.yml
downstream:
needs: [get_build_info]
runs-on: ubuntu-latest
steps:
# OK. `version` is defined in the reusable workflow
- run: echo '${{ needs.get_build_info.outputs.version }}'
# ERROR: `tag` is not defined in the reusable workflow
- run: echo '${{ needs.get_build_info.outputs.tag }}'
github.action_status
runner.debug
services.<service_id>.ports
on.workflow_call.inputs.<name>.description
and on.workflow_call.secrets.<name>.description
were incorrectly mandatory. They are actually optional.action.yml
in local actions. They were ignored in previous versions.{a: true, b: string}
, or it displayed {b: string, a: true}
for the same object type. This randomness was caused by random iteration of map values in Go.