actionlint ========== [![CI Status][ci-badge]][ci] [![API Document][apidoc-badge]][apidoc]
[actionlint][repo] is a static checker for GitHub Actions workflow files. [Try it online!][playground]
Features:
- Syntax check for workflow files to check unexpected or missing keys following [workflow syntax][syntax-doc]
- Strong type check for ${{ }} expressions to catch several semantic errors like access to not existing property,
type mismatches, ...
- Actions usage check to check that inputs at with: and outputs in steps.{id}.outputs are correct
- Reusable workflow check to check inputs/outputs/secrets of reusable workflows and workflow calls
- [shellcheck][] and [pyflakes][] integrations for scripts at run:
- Security checks; [script injection][script-injection-doc] by untrusted inputs, hard-coded credentials
- Other several useful checks; [glob syntax][filter-pattern-doc] validation, dependencies check for needs:,
runner label validation, cron syntax validation, ...
See the [full list][checks] of checks done by actionlint.
<img src="https://github.com/rhysd/ss/blob/master/actionlint/main.gif?raw=true" alt="actionlint reports 7 errors" width="806" height="492"/
Example of broken workflow:
on:
push:
branch: main
tags:
- 'v\d+'
jobs:
test:
strategy:
matrix:
os: [macos-latest, linux-latest]
runs-on: ${{ matrix.os }}
steps:- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: nodeversion: 18.x
- uses: actions/cache@v4
with: path: ~/.npm key: ${{ matrix.platform }}-node-${{ hashFiles('/package-lock.json') }} if: ${{ github.repository.permissions.admin == true }}
- run: npm install && npm test
**actionlint reports 7 errors:**
test.yaml:3:5: unexpected key "branch" for "push" section. expected one of "branches", "branches-ignore", "paths", "paths-ignore", "tags", "tags-ignore", "types", "workflows" [syntax-check] | 3 | branch: main | ^~~~~~~ test.yaml:5:11: character '\' is invalid for branch and tag names. only special characters [, ?, +, , \, ! can be escaped with \. see man git-check-ref-format for more details. note that regular expression is unavailable. note: filter pattern syntax is explained at https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#filter-pattern-cheat-sheet [glob] | 5 | - 'v\d+' | ^~~~ test.yaml:10:28: label "linux-latest" is unknown. available labels are "windows-latest", "windows-latest-8-cores", "windows-2025", "windows-2025-vs2026", windows-2022", "windows-11-arm", "ubuntu-slim", "ubuntu-latest", "ubuntu-latest-4-cores", "ubuntu-latest-8-cores", "ubuntu-latest-16-cores", "ubuntu-24.04", "ubuntu-24.04-arm", "ubuntu-22.04", "ubuntu-22.04-arm", "macos-latest", "macos-latest-xlarge", "macos-latest-large", "macos-26-intel", "macos-26-xlarge", "macos-26-large", "macos-26", "macos-15-intel", "macos-15-xlarge", "macos-15-large", "macos-15", "macos-14-xlarge", "macos-14-large", "macos-14", "self-hosted", "x64", "arm", "arm64", "linux", "macos", "windows". if it is a custom label for self-hosted runner, set list of labels in actionlint.yaml config file [runner-label] | 10 | os: [macos-latest, linux-latest] | ^~~~~~~~~~~~~ test.yaml:13:41: "github.event.headcommit.message" is potentially untrusted. avoid using it directly in inline scripts. instead, pass it through an environment variable. see https://docs.github.com/en/actions/reference/security/secure-use#good-practices-for-mitigating-script-injection-attacks for more details [expression] | 13 | - run: echo "Checking commit '${{ github.event.headcommit.message }}'" | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ test.yaml:17:11: input "nodeversion" is not defined in action "actions/setup-node@v4". available inputs are "always-auth", "architecture", "cache", "cache-dependency-path", "check-latest", "node-version", "node-version-file", "registry-url", "scope", "token" [action] | 17 | nodeversion: 18.x | ^~~~~~~~~~~~~ test.yaml:21:20: property "platform" is not defined in object type {os: string} [expression] | 21 | key: ${{ matrix.platform }}-node-${{ hashFiles('/package-lock.json') }} | ^~~~~~~~~~~~~~~ test.yaml:22:17: receiver of object dereference "permissions" must be type of object but got "string" [expression] | 22 | if: ${{ github.repository.permissions.admin == true }} | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~