actionlint is a static checker for GitHub Actions workflow files.

actionlint

CI Badge API Document

actionlint is a static checker for GitHub Actions workflow files.

Features:

  • Syntax check for workflow files to check unexpected or missing keys following workflow syntax
  • Strong type check for ${{ }} expressions to catch several semantic errors like access to not existing property, type mismatches, ...
  • shellcheck integration for scripts in run:
  • Other several useful checks; dependencies check for needs:, runner label validation, cron syntax validation, ...

See 'Checks' section for full list of checks done by actionlint.

Example of broken workflow:

on:
  push:
    branch: main
jobs:
  test:
    strategy:
      matrix:
        os: [macos-latest, linux-latest]
    runs-on: ${{ matrix.os }}
    steps:
      - uses: actions/checkout@v2
      - uses: actions/cache@v2
        with:
          path: ~/.npm
          key: ${{ matrix.platform }}-node-${{ hashFiles('**/package-lock.json') }}
        if: ${{ github.repository.permissions.admin == true }}
      - run: npm install && npm test

Output from actionlint:

example.yaml:3:5: unexpected key "branch" for "push" section. expected one of "types", "branches", "branches-ignore", "tags", "tags-ignore", "paths", "paths-ignore", "workflows" [syntax-check]
3|     branch: main
 |     ^~~~~~~
example.yaml:9:28: label "linux-latest" is unknown. available labels are "windows-latest", "windows-2019", "windows-2016", "ubuntu-latest", ... [runner-label]
9|         os: [macos-latest, linux-latest]
 |                            ^~~~~~~~~~~~~
example.yaml:17:17: receiver of object dereference "permissions" must be type of object but got "string" [expression]
17|         if: ${{ github.repository.permissions.admin == true }}
  |                 ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
example.yaml:16:20: property "platform" is not defined in object type {os: string} [expression]
16|           key: ${{ matrix.platform }}-node-${{ hashFiles('**/package-lock.json') }}
  |                    ^~~~~~~~~~~~~~~

actionlint tries to catch errors as much as possible and make false positives as minimal as possible.

Why?

  • Running workflow is very time consuming. You need to push the changes and wait until the workflow runs on GitHub even if it contains some trivial mistakes. act is useful to run the workflow locally. But it is not suitable for CI and still time consuming when your workflow gets larger.
  • Checks of workflow files by GitHub is very loose. It reports no error even if unexpected keys are in mappings (meant that some typos in keys). And also it reports no error when accessing to property which is actually not existing. For example matrix.foo when no foo is defined in matrix: section, it is evaluated to null and causes no error.
  • Some mistakes silently breaks workflow. Most common case I saw is specifying missing property to cache key. In the case cache silently does not work properly but workflow itself runs without error. So you might not notice the mistake forever.

Install

Homebrew on macOS

Tap this repository and install actionlint package.

brew tap "rhysd/actionlint" "https://github.com/rhysd/actionlint"
brew install actionlint

Prebuilt binaries

Download an archive file from releases page, unarchive it and put the executable file at a directory in $PATH.

Prebuilt binaries are built at each release by CI for the following OS and arch:

  • macOS (x86_64)
  • Linux (i386, x86_64, arm32, arm64)
  • Windows (i386, x86_64)
  • FreeBSD (i386, x86_64)

CI services

Please try the download script. It downloads the latest version of actionlint (actionlint.exe on Windows and actionlint on other OSes) to the current directory automatically. On GitHub Actions environment, it sets executable output to use the executable in the following steps easily.

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:
    strategy:
      matrix:
        os: [ubuntu-latest, macos-latest, windows-latest]
    runs-on: ${{ matrix.os }}
    steps:
      - uses: actions/checkout@v2
      - name: Download actionlint
        id: get_actionlint
        run: bash <(curl https://raw.githubusercontent.com/rhysd/actionlint/main/scripts/download-actionlint.bash)
        shell: bash
      - name: Check workflow files
        run: ${{ steps.get_actionlint.outputs.executable }}
        shell: bash

Build from source

Go toolchain is necessary. It builds the latest main branch.

# Install the latest version
go install github.com/rhysd/actionlint/cmd/actionlint@latest

# Install the head of main branch
go install github.com/rhysd/actionlint/cmd/actionlint

Usage

With no argument, actionlint finds all workflow files in the current repository and checks them.

actionlint

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

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

See actionlint -h for all flags and options.

Checks

This section describes all checks done by actionlint with example input and output.

Note that actionlint focuses on catching mistakes in workflow files. If you want some code style checks, please consider to use a general YAML checker like yamllint.

Unexpected keys

Example input:

on: push
jobs:
  test:
    runs-on: ubuntu-latest
    step:

Output:

test.yaml:5:5: unexpected key "step" for "job" section. expected one of "name", "needs", "runs-on", "permissions", "environment", "concurrency", "outputs", "env", "defaults", "if", "steps", "timeout-minutes", "strategy", "continue-on-error", "container", "services" [syntax-check]
5|     step:
 |     ^~~~~

Workflow syntax defines what keys can be defined in which mapping object. When other keys are defined, they are simply ignored and don't affect workflow behavior. It means typo in keys is not detected by GitHub.

actionlint can detect unexpected keys while parsing workflow syntax and report them as error.

Missing required keys or key duplicates

Example input:

on: push
jobs:
  test:
    steps:
      - run: echo 'hello'
  TEST:
    runs-on: ubuntu-latest
    steps:
      - run: echo 'bye'

Output:

test.yaml:6:3: key "test" is duplicate in "jobs" section. previously defined at line:3,col:3. note that key names are case insensitive [syntax-check]
6|   TEST:
 |   ^~~~~
test.yaml:4:5: "runs-on" section is missing in job "test" [syntax-check]
4|     steps:
 |     ^~~~~~

Some mappings must include specific keys. For example, job mapping must include runs-on: and steps:.

And duplicate in keys is not allowed. In workflow syntax, comparing keys is case insensitive. For example, job ID test in lower case and job ID TEST in upper case are not able to exist in the same workflow.

actionlint checks these missing required keys and duplicate of keys while parsing, and reports an error.

Unexpected empty mappings

Example input:

on: push
jobs:

Output:

test.yaml:2:6: "jobs" section should not be empty. please remove this section if it's unnecessary [syntax-check]
2| jobs:
 |      ^

Some mappings and sequences should not be empty. For example, steps: must include at least one step.

actionlint checks such mappings and sequences are not empty while parsing, and reports the empty mappings and sequences as error.

Unexpected mapping values

Example input:

on: push
jobs:
  test:
    runs-on: ubuntu-latest
    permissions:
      issues: foo
    continue-on-error: foo
    steps:
      - run: echo 'hello'

Output:

test.yaml:6:15: permission must be one of "none", "read", "write" but got "foo" [syntax-check]
6|       issues: foo
 |               ^~~
test.yaml:7:24: expecting a string with ${{...}} expression or boolean literal "true" or "false", but found plain text node [syntax-check]
7|     continue-on-error: foo
 |                        ^~~

Some mapping's values are restricted to some constant strings. For example, values of permissions: mappings should be one of none, read, write. And several mapping values expect boolean value like true or false.

actionlint checks such constant strings are used properly while parsing, and reports an error when unexpected string value is specified.

Syntax check for expression ${{ }}

Example input:

on: push
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      # " is not available for string literal delimiter
      - run: echo '${{ "hello" }}'
      # + operator does not exist
      - run: echo '${{ 1 + 1 }}'
      # Missing ')' paren
      - run: echo "${{ toJson(hashFiles('**/lock', '**/cache/') }}"
      # unexpected end of input
      - run: echo '${{ github.event. }}'

Output:

', '=', '&', ... [expression] 7| - run: echo '${{ "hello" }}' | ^~~~~~ test.yaml:9:27: got unexpected character '+' while lexing expression, expecting '_', '\'', '}', '(', ')', '[', ']', '.', '!', '<', '>', '=', '&', ... [expression] 9| - run: echo '${{ 1 + 1 }}' | ^ test.yaml:11:65: unexpected end of input while parsing arguments of function call. expecting ",", ")" [expression] 11| - run: echo "${{ toJson(hashFiles('**/lock', '**/cache/') }}" | ^~~ test.yaml:13:38: unexpected end of input while parsing object property dereference like 'a.b' or array element dereference like 'a.*'. expecting "IDENT", "*" [expression] 13| - run: echo '${{ github.event. }}' | ^~~ ">
test.yaml:7:25: got unexpected character '"' while lexing expression, expecting '_', '\'', '}', '(', ')', '[', ']', '.', '!', '<', '>', '=', '&', ... [expression]
7|       - run: echo '${{ "hello" }}'
 |                         ^~~~~~
test.yaml:9:27: got unexpected character '+' while lexing expression, expecting '_', '\'', '}', '(', ')', '[', ']', '.', '!', '<', '>', '=', '&', ... [expression]
9|       - run: echo '${{ 1 + 1 }}'
 |                           ^
test.yaml:11:65: unexpected end of input while parsing arguments of function call. expecting ",", ")" [expression]
11|       - run: echo "${{ toJson(hashFiles('**/lock', '**/cache/') }}"
  |                                                                 ^~~
test.yaml:13:38: unexpected end of input while parsing object property dereference like 'a.b' or array element dereference like 'a.*'. expecting "IDENT", "*" [expression]
13|       - run: echo '${{ github.event. }}'
  |                                      ^~~

actionlint lexes and parses expression in ${{ }} following the expression syntax document. It can detect many syntax errors like invalid characters, missing parens, unexpected end of input, ...

Type checks for expression syntax in ${{ }}

Example input:

on: push
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      # Basic type error like index access to object
      - run: echo '${{ env[0] }}'
      # Properties in objects are strongly typed. So missing property can be caught
      - run: echo '${{ job.container.os }}'
      # github.repository is string. So accessing .owner is invalid
      - run: echo '${{ github.repository.owner }}'

Output:

test.yaml:7:28: property access of object must be type of string but got "number" [expression]
7|       - run: echo '${{ env[0] }}'
 |                            ^~
test.yaml:9:24: property "os" is not defined in object type {id: string; network: string} [expression]
9|       - run: echo '${{ job.container.os }}'
 |                        ^~~~~~~~~~~~~~~~
test.yaml:11:24: receiver of object dereference "owner" must be type of object but got "string" [expression]
11|       - run: echo '${{ github.repository.owner }}'
  |                        ^~~~~~~~~~~~~~~~~~~~~~~

Type checks for expression syntax in ${{ }} are done by semantics checker. Note that actual type checks by GitHub Actions runtime is loose. For example any object value can be assigned into string value as string "Object". But such loose conversions are bugs in almost all cases. actionlint checks types more strictly.

There are two types of object types internally. One is an object which is strict for properties, which causes a type error when trying to access to unknown properties. And another is an object which is not strict for properties, which allows to access to unknown properties. In the case, accessing to unknown property is typed as any.

When the type check cannot be done statically, the type is deduced to any (e.g. return type from toJSON()).

And ${{ }} can be used for expanding values.

Example input:

on: push
jobs:
  test:
    strategy:
      matrix:
        env_string:
          - 'FOO=BAR'
          - 'FOO=PIYO'
        env_object:
          - FOO: BAR
          - FOO: PIYO
    runs-on: ubuntu-latest
    steps:
      # Expanding object at 'env:' section
      - run: echo "$FOO"
        env: ${{ matrix.env_object }}
      # String value cannot be expanded as object
      - run: echo "$FOO"
        env: ${{ matrix.env_string }}

Output:

test.yaml:19:14: type of expression at "env" must be object but found type string [expression]
19|         env: ${{ matrix.env_string }}
  |              ^~~

In above example, environment variables mapping is expanded at env: section. actionlint checks type of the expanded value.

Contexts and built-in functions

Example input:

on: push
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      # Access to undefined context
      - run: echo '${{ unknown_context }}'
      # Access to undefined property of context
      - run: echo '${{ github.events }}'
      # Calling undefined function (start's'With is correct)
      - run: echo "${{ startWith('hello, world', 'lo,') }}"
      # Wrong number of arguments
      - run: echo "${{ startsWith('hello, world') }}"
      # Wrong type of parameter
      - run: echo "${{ startsWith('hello, world', github.event) }}"
      # Function overloads can be handled properly. contains() has string version and array version
      - run: echo "${{ contains('hello, world', 'lo,') }}"
      - run: echo "${{ contains(github.event.labels.*.name, 'enhancement') }}"
      # format() has special check for formating string
      - run: echo "${{ format('{0}{1}', 1, 2, 3) }}"

Output:

bool" takes 2 parameters but 1 arguments are provided [expression] 13| - run: echo "${{ startsWith('hello, world') }}" | ^~~~~~~~~~~~~~~~~~ test.yaml:15:51: 2nd argument of function call is not assignable. "object" cannot be assigned to "string". called function type is "startsWith(string, string) -> bool" [expression] 15| - run: echo "${{ startsWith('hello, world', github.event) }}" | ^~~~~~~~~~~~~ test.yaml:20:24: format string "{0}{1}" contains 2 placeholders but 3 arguments are given to format [expression] 20| - run: echo "${{ format('{0}{1}', 1, 2, 3) }}" | ^~~~~~~~~~~~~~~~ ">
test.yaml:7:24: undefined variable "unknown_context". available variables are "env", "github", "job", "matrix", "needs", "runner", "secrets", "steps", "strategy" [expression]
7|       - run: echo '${{ unknown_context }}'
 |                        ^~~~~~~~~~~~~~~
test.yaml:9:24: property "events" is not defined in object type {workspace: string; env: string; event_name: string; event_path: string; ...} [expression]
9|       - run: echo '${{ github.events }}'
 |                        ^~~~~~~~~~~~~
test.yaml:11:24: undefined function "startWith". available functions are "always", "cancelled", "contains", "endswith", "failure", "format", "fromjson", "hashfiles", "join", "startswith", "success", "tojson" [expression]
11|       - run: echo "${{ startWith('hello, world', 'lo,') }}"
  |                        ^~~~~~~~~~~~~~~~~
test.yaml:13:24: number of arguments is wrong. function "startsWith(string, string) -> bool" takes 2 parameters but 1 arguments are provided [expression]
13|       - run: echo "${{ startsWith('hello, world') }}"
  |                        ^~~~~~~~~~~~~~~~~~
test.yaml:15:51: 2nd argument of function call is not assignable. "object" cannot be assigned to "string". called function type is "startsWith(string, string) -> bool" [expression]
15|       - run: echo "${{ startsWith('hello, world', github.event) }}"
  |                                                   ^~~~~~~~~~~~~
test.yaml:20:24: format string "{0}{1}" contains 2 placeholders but 3 arguments are given to format [expression]
20|       - run: echo "${{ format('{0}{1}', 1, 2, 3) }}"
  |                        ^~~~~~~~~~~~~~~~

Contexts and built-in functions are strongly typed. Typos in property access of contexts and function names can be checked. And invalid function calls like wrong number of arguments or type mismatch at parameter also can be checked thanks to type checker.

The semantics checker can properly handle that

  • some functions are overloaded (e.g. contains(str, substr) and contains(array, item))
  • some parameters are optional (e.g. join(strings, sep) and join(strings))
  • some parameters are repeatable (e.g. hashFiles(file1, file2, ...))

In addition, format() function has special check for placeholders in the first parameter which represents formatting string.

Note that context names and function names are case insensitive. For example, toJSON and toJson are the same function.

Contextual typing for steps. objects

Example input:

on: push
jobs:
  test:
    runs-on: ubuntu-latest
    outputs:
      # Step outputs can be used in job outputs since this section is evaluated after all steps were run
      foo: '${{ steps.get_value.outputs.name }}'
    steps:
      # Access to undefined step outputs
      - run: echo '${{ steps.get_value.outputs.name }}'
      # Outputs are set here
      - run: echo '::set-output name=foo::value'
        id: get_value
      # OK
      - run: echo '${{ steps.get_value.outputs.name }}'
      # OK
      - run: echo '${{ steps.get_value.conclusion }}'
  other:
    runs-on: ubuntu-latest
    steps:
      # Access to undefined step outputs. Step objects are job-local
      - run: echo '${{ steps.get_value.outputs.name }}'

Output:

test.yaml:10:24: property "get_value" is not defined in object type {} [expression]
10|       - run: echo '${{ steps.get_value.outputs.name }}'
  |                        ^~~~~~~~~~~~~~~~~~~~~~~~~~~~
test.yaml:22:24: property "get_value" is not defined in object type {} [expression]
22|       - run: echo '${{ steps.get_value.outputs.name }}'
  |                        ^~~~~~~~~~~~~~~~~~~~~~~~~~~~

Outputs of step can be accessed via steps. objects. The steps context is dynamic:

  • Accessing to the outputs before running the step are null
  • Outputs of steps only in the job can be accessed. It cannot access to steps across jobs

It is actually common mistake to access to the wrong step outputs since people often forget fixing placeholders on copying&pasting steps.

actionlint can catch the invalid accesses to step outputs and reports them as errors.

Contextual typing for matrix object

Example input:

on: push
jobs:
  test:
    strategy:
      matrix:
        os: [ubuntu-latest, windows-latest]
        node: [14, 15]
        package:
          - name: 'foo'
            optional: true
          - name: 'bar'
            optional: false
        include:
          - node: 15
            npm: 7.5.4
    runs-on: ${{ matrix.os }}
    steps:
      # Access to undefined matrix value
      - run: echo '${{ matrix.platform }}'
      # Matrix value is strongly typed. Below line causes an error since matrix.package is {name: string, optional: bool}
      - run: echo '${{ matrix.package.dev }}'
      # OK
      - run: |
          echo 'os: ${{ matrix.os }}'
          echo 'node version: ${{ matrix.node }}'
          echo 'package: ${{ matrix.package.name }} (optional=${{ matrix.package.optional }})'
      # Additional matrix values in 'include:' are supported
      - run: echo 'npm version is specified'
        if: ${{ contains(matrix.npm, '7.5') }}
  test2:
    runs-on: ubuntu-latest
    steps:
      # Matrix values in other job is not accessible
      - run: echo '${{ matrix.os }}'

Output:

test.yaml:19:24: property "platform" is not defined in object type {os: string; node: number; package: {name: string; optional: bool}; npm: string} [expression]
19|       - run: echo '${{ matrix.platform }}'
  |                        ^~~~~~~~~~~~~~~
test.yaml:21:24: property "dev" is not defined in object type {name: string; optional: bool} [expression]
21|       - run: echo '${{ matrix.package.dev }}'
  |                        ^~~~~~~~~~~~~~~~~~
test.yaml:34:24: property "os" is not defined in object type {} [expression]
34|       - run: echo '${{ matrix.os }}'
  |                        ^~~~~~~~~

Types of matrix context is contextually checked by the semantics checker. Type of matrix values in matrix: section is deduced from element values of its array. When the matrix value is an array of objects, objects' properties are checked strictly like package.name in above example.

When type of the array elements is not persistent, type of the matrix value falls back to any.

strategy:
  matrix:
    foo:
      - 'string value'
      - 42
      - {aaa: true, bbb: null}
    bar:
      - [42]
      - [true]
      - [{aaa: true, bbb: null}]
      - []
steps:
  # matrix.foo is any type value
  - run: echo ${{ matrix.foo }}
  # matrix.bar is array type value
  - run: echo ${{ matrix.bar[0] }}

Contextual typing for needs object

Example input:

on: push
jobs:
  install:
    outputs:
      installed: '...'
    runs-on: ubuntu-latest
    steps:
      - run: echo 'install something'
  prepare:
    outputs:
      prepared: '...'
    runs-on: ubuntu-latest
    steps:
      - run: echo 'parepare something'
      # ERROR: Outputs in other job is not accessble
      - run: echo '${{ needs.prepare.outputs.prepared }}'
  build:
    needs: [install, prepare]
    outputs:
      built: '...'
    runs-on: ubuntu-latest
    steps:
      # OK: Accessing to job results
      - run: echo 'build something with ${{ needs.install.outputs.installed }} and ${{ needs.prepare.outputs.prepared }}'
      # ERROR: Accessing to undefined output cases an error
      - run: echo '${{ needs.install.outputs.foo }}'
      # ERROR: Accessing to undefined job ID
      - run: echo '${{ needs.some_job }}'
  other:
    runs-on: ubuntu-latest
    steps:
      # ERROR: Cannot access to outptus across jobs
      - run: echo '${{ needs.build.outputs.built }}'

Output:

test.yaml:16:24: property "prepare" is not defined in object type {} [expression]
16|       - run: echo '${{ needs.prepare.outputs.prepared }}'
  |                        ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
test.yaml:26:24: property "foo" is not defined in object type {installed: string} [expression]
26|       - run: echo '${{ needs.install.outputs.foo }}'
  |                        ^~~~~~~~~~~~~~~~~~~~~~~~~
test.yaml:28:24: property "some_job" is not defined in object type {install: {outputs: {installed: string}; result: string}; prepare: {outputs: {prepared: string}; result: string}} [expression]
28|       - run: echo '${{ needs.some_job }}'
  |                        ^~~~~~~~~~~~~~
test.yaml:33:24: property "build" is not defined in object type {} [expression]
33|       - run: echo '${{ needs.build.outputs.built }}'
  |                        ^~~~~~~~~~~~~~~~~~~~~~~~~

Job dependencies can be defined at needs:. A job runs after all jobs defined in needs: are done. Outputs from the jobs can be accessed only from jobs following them via needs context.

actionlint defines type of needs variable contextually looking at each job's outputs: section and needs: section.

shellcheck integration

Example input:

on: push
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - run: echo $FOO
  test-win:
    runs-on: windows-latest
    steps:
      # Shell on Windows is PowerShell by default.
      # shellcheck is not run in this case.
      - run: echo $FOO
      # This script is run with bash due to 'shell:' configuration
      - run: echo $FOO
        shell: bash

Output:

test.yaml:6:9: shellcheck reported issue in this script: SC2086:info:1:6: Double quote to prevent globbing and word splitting [shellcheck]
6|       - run: echo $FOO
 |         ^~~~
test.yaml:14:9: shellcheck reported issue in this script: SC2086:info:1:6: Double quote to prevent globbing and word splitting [shellcheck]
14|       - run: echo $FOO
  |         ^~~~

shellcheck is a famous linter for ShellScript. actionlint runs shellcheck for scripts at run: step in workflow.

actionlint detects which shell is used to run the scripts following the documentation. On Linux or macOS, the default shell is bash and on Windows it is pwsh. Shell can be configured by shell: configuration at workflow level or job level. Each step can configure shell to run scripts by shell:.

actionlint remembers the default shell and checks what OS the job runs on. Only when the shell is bash or sh, actionlint applies shellcheck to scripts.

By default, actionlint checks if shellcheck command exists in your system and uses it when it is found. The -shellcheck option on running actionlint command specifies the executable path of shellcheck. Setting empty string by shellcheck= disables shellcheck integration explicitly.

Since both ${{ }} expression syntax and ShellScript's variable access $FOO use $, remaining ${{ }} confuses shellcheck. To avoid it, actionlint replaces ${{ }} with underscores. For example echo '${{ matrix.os }}' is replaced with echo '________________'.

Job dependencies validation

Example input:

on: push
jobs:
  prepare:
    needs: [build]
    runs-on: ubuntu-latest
    steps:
      - run: echo 'prepare'
  install:
    needs: [prepare]
    runs-on: ubuntu-latest
    steps:
      - run: echo 'install'
  build:
    needs: [install]
    runs-on: ubuntu-latest
    steps:
      - run: echo 'build'

Output:

"prepare", "prepare" -> "build", "build" -> "install" [job-needs] 8| install: | ^~~~~~~~ ">
test.yaml:8:3: cyclic dependencies in "needs" configurations of jobs are detected. detected cycle is "install" -> "prepare", "prepare" -> "build", "build" -> "install" [job-needs]
8|   install:
 |   ^~~~~~~~

Job dependencies can be defined at needs:. If cyclic dependencies exist, jobs never start to run. actionlint detects cyclic dependencies in needs: sections of jobs and reports it as error.

actionlint also detects undefined jobs and duplicate jobs in needs: section.

Example input:

on: push
jobs:
  foo:
    needs: [bar, BAR]
    runs-on: ubuntu-latest
    steps:
      - run: echo 'hi'
  bar:
    needs: [unknown]
    runs-on: ubuntu-latest
    steps:
      - run: echo 'hi'

Output:

test.yaml:4:18: job ID "BAR" duplicates in "needs" section. note that job ID is case insensitive [job-needs]
4|     needs: [bar, BAR]
 |                  ^~~~
test.yaml:8:3: job "bar" needs job "unknown" which does not exist in this workflow [job-needs]
8|   bar:
 |   ^~~~

Matrix values

Example input:

on: push
jobs:
  test:
    strategy:
      matrix:
        node: [10, 12, 14, 14]
        os: [ubuntu-latest, macos-latest]
        exclude:
          - node: 13
            os: ubuntu-latest
          - node: 10
            platform: ubuntu-latest
    runs-on: ${{ matrix.os }}
    steps:
      - run: echo ...

Output:

test.yaml:6:28: duplicate value "14" is found in matrix "node". the same value is at line:6,col:24 [matrix]
6|         node: [10, 12, 14, 14]
 |                            ^~~
test.yaml:9:19: value "13" in "exclude" does not exist in matrix "node" combinations. possible values are "10", "12", "14", "14" [matrix]
9|           - node: 13
 |                   ^~
test.yaml:12:13: "platform" in "exclude" section does not exist in matrix. available matrix configurations are "node", "os" [matrix]
12|             platform: ubuntu-latest
  |             ^~~~~~~~~

matrix: defines combinations of multiple values. Nested include: and exclude: can add/remove specific combination of matrix values. actionlint checks

  • values in exclude: appear in matrix: or include:
  • duplicate in variations of matrix values

Webhook events validation

Example input:

on:
  push:
    # Unexpected filter. 'branches' is correct
    branch: foo
  issues:
    # Unexpected type. 'opened' is correct
    types: created
  pullreq:

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - run: echo ...

Output:

test.yaml:4:5: unexpected key "branch" for "push" section. expected one of "types", "branches", "branches-ignore", "tags", "tags-ignore", ... [syntax-check]
4|     branch: foo
 |     ^~~~~~~
test.yaml:7:12: invalid activity type "created" for "issues" Webhook event. available types are "opened", "edited", "deleted", "transferred", ... [events]
7|     types: created
 |            ^~~~~~~
test.yaml:8:3: unknown Webhook event "pullreq". see https://docs.github.com/en/actions/reference/events-that-trigger-workflows#webhook-events for list of all Webhook event names [events]
8|   pullreq:
 |   ^~~~~~~~

At on:, Webhook events can be specified to trigger the workflow. Webhook event documentation defines which Webhook events are available and what types can be specified at types: for each event.

actionlint validates the Webhook configurations:

  • unknown Webhook event name
  • unknown type for Webhook event
  • invalid filter names

CRON syntax check at schedule:

Example input:

on:
  schedule:
    # Cron syntax is not correct
    - cron: '0 */3 * *'
    # Interval of scheduled job is too small (job runs too frequently)
    - cron: '* */3 * * *'

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - run: echo ...

Output:

test.yaml:4:13: invalid CRON format "0 */3 * *" in schedule event: Expected exactly 5 fields, found 4: 0 */3 * * [events]
4|     - cron: '0 */3 * *'
 |             ^~
test.yaml:6:13: scheduled job runs too frequently. it runs once per 60 seconds [events]
6|     - cron: '* */3 * * *'
 |             ^~

To trigger a workflow in specific interval, scheduled event can be defined in POSIX CRON syntax.

actionlint checks the CRON syntax and frequency of running the job. When a job is run more frequently than once per 1 minute, actionlint reports it as error.

Runner labels

Example input:

on: push
jobs:
  test:
    strategy:
      matrix:
        runner:
          # OK
          - macos-latest
          # ERROR: Unknown runner
          - linux-latest
          # OK: Preset labels for self-hosted runner
          - [self-hosted, linux, x64]
          # OK: Single preset label for self-hosted runner
          - arm64
          # ERROR: Unknown label "gpu". Custom label must be defined in actionlint.yaml config file
          - gpu
    runs-on: ${{ matrix.runner }}
    steps:
      - run: echo ...

  test2:
    # ERROR: Too old macOS worker
    runs-on: macos-10.13
    steps:
      - run: echo ...

Output:

test.yaml:10:13: label "linux-latest" is unknown. available labels are "windows-latest", "windows-2019", "windows-2016", "ubuntu-latest", ... [runner-label]
10|           - linux-latest
  |             ^~~~~~~~~~~~
test.yaml:16:13: label "gpu" is unknown. available labels are "windows-latest", "windows-2019", "windows-2016", "ubuntu-latest", ... [runner-label]
16|           - gpu
  |             ^~~
test.yaml:23:14: label "macos-10.13" is unknown. available labels are "windows-latest", "windows-2019", "windows-2016", "ubuntu-latest", ... [runner-label]
23|     runs-on: macos-10.13
  |              ^~~~~~~~~~~

GitHub Actions provides two kinds of job runners, GitHub-hosted runner and self-hosted runner. Each runner has one or more labels. GitHub Actions runtime finds a proper runner based on label(s) specified at runs-on: to run the job. So specifying proper labels at runs-on: is important.

actionlint checks proper label is used at runs-on: configuration. Even if an expression is used in the section like runs-on: ${{ matrix.foo }}, actionlint parses the expression and resolves the possible values, then validates the values.

When you define some custom labels for your self-hosted runner, actionlint does not know the labels. Please set the label names in actionlint.yaml configuration file to let actionlint know them.

Action format in uses:

Example input:

on: push
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      # ERROR: ref is missing
      - uses: actions/checkout
      # ERROR: owner name is missing
      - uses: checkout@v2
      # ERROR: tag is empty
      - uses: 'docker://image:'
      # ERROR: local action does not exist
      - uses: ./github/actions/my-action

Output:

test.yaml:7:15: specifying action "actions/checkout" in invalid format because ref is missng. available formats are "{owner}/{repo}@{ref}" or "{owner}/{repo}/{path}@{ref}" [action]
7|       - uses: actions/checkout
 |               ^~~~~~~~~~~~~~~~
test.yaml:9:15: specifying action "checkout@v2" in invalid format because owner is missing. available formats are "{owner}/{repo}@{ref}" or "{owner}/{repo}/{path}@{ref}" [action]
9|       - uses: checkout@v2
 |               ^~~~~~~~~~~
test.yaml:11:15: tag of Docker action should not be empty: "docker://image" [action]
11|       - uses: 'docker://image:'
  |               ^~~~~~~~~~~~~~~~~
test.yaml:13:15: Neither action.yaml nor action.yml is found in directory "github/actions/my-action" [action]
13|       - uses: ./github/actions/my-action
  |               ^~~~~~~~~~~~~~~~~~~~~~~~~~

Action needs to be specified in a format defined in the document. There are 3 types of actions:

  • action hosted on GitHub: owner/repo/path@ref
  • local action: ./path/to/my-action
  • Docker action: docker://image:tag

actionlint checks values at uses: sections follow one of these formats.

Local action inputs validation at with:

.github/actions/my-action/action.yaml:

name: 'My action'
author: 'rhysd '
description: 'my action'

inputs:
  name:
    description: your name
    default: anonymous
  message:
    description: message to this action
    required: true
  addition:
    description: additional information
    required: false

runs:
  using: 'node14'
  main: 'index.js'

Example input:

on: push
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      # missing required input "message"
      - uses: ./.github/actions/my-action
      # unexpected input "additions"
      - uses: ./.github/actions/my-action
        with:
          name: rhysd
          message: hello
          additions: foo, bar

Output:

test.yaml:7:15: missing input "message" which is required by action "My action" defined at "./.github/actions/my-action" [action]
7|       - uses: ./.github/actions/my-action
 |               ^~~~~~~~~~~~~~~~~~~~~~~~~~~
test.yaml:13:11: input "additions" is not defined in action "./.github/actions/my-action" defined at "My action". available inputs are "addition", "message", "name" [action]
13|           additions: foo, bar
  |           ^~~~~~~~~~

When a local action is run in uses: of step:, actionlint reads action.yaml file in the local action directory and validates inputs at with: in the workflow are correct. Missing required inputs and unexpected inputs can be detected.

Shell name validation at shell:

Example input:

on: push
jobs:
  linux:
    runs-on: ubuntu-latest
    steps:
      - run: echo 'hello'
        # ERROR: Unavailable shell
        shell: dash
      - run: echo 'hello'
        # ERROR: 'powershell' is only available on Windows
        shell: powershell
      - run: echo 'hello'
        # OK: 'powershell' is only available on Windows
        shell: powershell
  mac:
    runs-on: macos-latest
    defaults:
      run:
        # ERROR: default config is also checked. fish is not supported
        shell: fish
    steps:
      - run: echo 'hello'
        # OK: Custom shell
        shell: 'perl {0}'
  windows:
    runs-on: windows-latest
    steps:
      - run: echo 'hello'
        # ERROR: 'sh' is only available on Windows
        shell: sh

Output:

test.yaml:8:16: shell name "dash" is invalid. available names are "bash", "pwsh", "python", "sh" [shell-name]
8|         shell: dash
 |                ^~~~
test.yaml:11:16: shell name "powershell" is invalid on macOS or Linux. available names are "bash", "pwsh", "python", "sh" [shell-name]
11|         shell: powershell
  |                ^~~~~~~~~~
test.yaml:14:16: shell name "powershell" is invalid on macOS or Linux. available names are "bash", "pwsh", "python", "sh" [shell-name]
14|         shell: powershell
  |                ^~~~~~~~~~
test.yaml:20:16: shell name "fish" is invalid. available names are "bash", "pwsh", "python", "sh" [shell-name]
20|         shell: fish
  |                ^~~~
test.yaml:30:16: shell name "sh" is invalid on Windows. available names are "bash", "pwsh", "python", "cmd", "powershell" [shell-name]
30|         shell: sh
  |                ^~

Available shells for runners are defined in the documentation. actionlint checks shell names at shell: configuration are properly using the available shells.

Job ID and step ID uniqueness

Example input:

on: push
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - run: echo 'hello'
        id: step_id
      - run: echo 'bye'
        # ERROR: Duplicate of step ID
        id: STEP_ID
  # ERROR: Duplicate of job ID
  TEST:
    runs-on: ubuntu-latest
    steps:
      - run: echo 'hello'
        # OK. Step ID uniqueness is job-local
        id: step_id

Output:

test.yaml:12:3: key "test" is duplicate in "jobs" section. previously defined at line:3,col:3. note that key names are case insensitive [syntax-check]
12|   TEST:
  |   ^~~~~
test.yaml:10:13: step ID "line:7,col:13" duplicates. previously defined at STEP_ID. step ID must be unique within a job. note that step ID is case insensitive [step-id]
10|         id: STEP_ID
  |             ^~~~~~~

Job IDs and step IDs in each job must be unique. IDs are compared in case insensitive. actionlint checks all job IDs and step IDs and reports errors when some IDs duplicate.

Hardcoded credentials

Example input:

on: push
jobs:
  test:
    runs-on: ubuntu-latest
    container:
      image: 'example.com/owner/image'
      credentials:
        username: user
        # ERROR: Hardcoded password
        password: pass
    services:
      redis:
        image: redis
        credentials:
          username: user
          # ERROR: Hardcoded password
          password: pass
    steps:
      - run: echo 'hello'

Output:

test.yaml:10:19: "password" section in "container" section should be specified via secrets. do not put password value directly [credentials]
10|         password: pass
  |                   ^~~~
test.yaml:17:21: "password" section in "redis" service should be specified via secrets. do not put password value directly [credentials]
17|           password: pass
  |                     ^~~~

Credentials for container can be put in container: configuration. Password should be put in secrets and the value should be expanded with ${{ }} syntax at password:. actionlint checks hardcoded credentials and reports them as error.

Environment variable names

Example input:

on: push
jobs:
  test:
    runs-on: ubuntu-latest
    env:
      FOO=BAR: foo
      FOO BAR: foo
    steps:
      - run: echo 'hello'

Output:

actionlint command can generate a default configuration.

actionlint -init-config
vim .github/actionlint.yaml

Since the author tries to keep configuration file as minimal as possible, currently only one item can be configured.

self-hosted-runner:
  # Labels of self-hosted runner in array of string
  labels:
    - linux.2xlarge
    - windows-latest-xl
    - linux-multi-gpu
  • self-hosted-runner: Configuration for your self-hosted runner environment
    • labels: Label names added to your self-hosted runners as list of string

Note that configuration file is optional. The author tries to keep configuration file as minimal as possible not to bother users to configure behavior of actionlint. Running actionlint without configuration file would work fine in most cases.

Use actionlint as library

actionlint can be used from Go programs. See the documentation to know the list of all APIs. It contains workflow file parser built on top of go-yaml/yaml, expression ${{ }} lexer/parser/checker, etc. Followings are unexhaustive list of interesting APIs.

  • Linter manages linter lifecycle and applies checks to given files. If you want to run actionlint checks in your program, please use this struct.
  • Project and Projects detect a project (Git repository) in a given directory path and find configuration in it.
  • Config represents structure of actionlint.yaml config file. It can be decoded by go-yaml/yaml library.
  • Workflow, Job, Step, ... are nodes of workflow syntax tree. Workflow is a root node.
  • Parse() parses given contents into a workflow syntax tree. It tries to find syntax errors as much as possible and returns found errors as slice.
  • Pass is a visitor to traverse a workflow syntax tree. Multiple passes can be applied at single pass using Visitor.
  • Rule is an interface for rule checkers and RuneBase is a base struct to implement a rule checker.
    • RuleExpression is a rule checker to check expression syntax in ${{ }}.
    • RuleShellcheck is a rule checker to apply shellcheck command to run: sections and collect errors from it.
    • RuleJobNeeds is a rule checker to check dependencies in needs: section. It can detect cyclic dependencies.
    • ...
  • ExprLexer lexes expression syntax in ${{ }} and returns slice of Token.
  • ExprParser parses given slice of Token and returns syntax tree for expression in ${{ }}. ExprNode is an interface for nodes in the expression syntax tree.
  • ExprType is an interface of types in expression syntax ${{ }}. ObjectType, ArrayType, StringType, NumberType, ... are structs to represent actual types of expression.
  • ExprSemanticsChecker checks semantics of expression syntax ${{ }}. It traverses given expression syntax tree and deduces its type, checking types and resolving variables (contexts).

Note that the version of this repository is for command line tool actionlint. So it does not represent version of the library, meant that patch version bump may introduce some breaking changes.

Testing

  • All examples in 'Checks' section are tested in example_test.go
  • I cloned GitHub top 1000 repositories and extracted 1400+ workflow files. And I tried actionlint with the collected workflow files. All bugs found while the trial were fixed and I confirmed no more false positives.

Bug reporting

When you 're seeing some bugs or false positives, it is helpful to file a new issue with a minimal example of input. Giving me some feedbacks like feature requests or idea of additional checks is also welcome.

License

actionlint is distributed under the MIT license.

Owner
Linda_pp
int main() { return (U'Ο‰'); } // A dog enjoying software development (with bug)
Linda_pp
Comments
  • Reusable workflows referenced by path (which is valid) are not being allowed

    Reusable workflows referenced by path (which is valid) are not being allowed

    At https://docs.github.com/en/actions/using-workflows/reusing-workflows#calling-a-reusable-workflow it is stated:

    You reference reusable workflow files using one of the following syntaxes:

    • {owner}/{repo}/{path}/{filename}@{ref} for reusable workflows in public repositories.
    • ./{path}/{filename} for reusable workflows in the same repository.

    I have just created a reusable workflow in the same repository as the caller workflow (so matching the second bullet point from above) and am calling it like so:

    dp_deploy_dev:
        uses: ./.github/workflows/deploy-asset.yml
    

    actionlint is returning this error:

    .github/workflows/ci.yml:378:11: reusable workflow call "./.github/workflows/deploy-asset.yml" at "uses" is not following the format "owner/repo/path/to/workflow.yml@ref". see https://docs.github.com/en/actions/learn-github-actions/reusing-workflows for more details [workflow-call]
        |
    378 |     uses: ./.github/workflows/deploy-asset.yml
        |           ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    ------
    

    Given the documentation quoted above I'm pretty certain this is valid syntax and actionlint is raising a false positive. Would you agree?

  • About the lint behavior of scheduled job

    About the lint behavior of scheduled job

    When I run actionlint, the output is as follows:

    .github/workflows/schedule.yml:4:13: scheduled job runs too frequently. it runs once per 60 seconds [events]
      |
    4 |     - cron: '* * * * *'
      |             ^~
    

    According to the documentation at https://docs.github.com/en/actions/reference/events-that-trigger-workflows#scheduled-events, it seems that '* * * * *' appears to be the correct format.

    Which is correct, GitHub Actions document or actionlint implementation?

  • false negative on use `env` in `env` definition

    false negative on use `env` in `env` definition

    this commit passes actionlint

    https://github.com/kazuk/sbom-ghr/pull/2/commits/fe18f2d3e424548e791ab9335ea4bc63e662a4e4

    but The workflow is not valid. shown as bellow.

    The workflow is not valid. .github/workflows/rust.yml (Line: 18, Col: 18): Unrecognized named-value: 'env'. Located at position 1 within expression: env.KEY_PREFIX .github/workflows/rust.yml (Line: 43, Col: 22): Unrecognized named-value: 'env'. Located at position 1 within expression: env.KEY_PREFIX
    
  • Add way to customize shellcheck invocation

    Add way to customize shellcheck invocation

    Right now, I don't see a way to customize shellcheck invocation (I want to exclude additional check).

    The list of excluded checks is hard-coded, and it is not using a configuration file:

    https://github.com/rhysd/actionlint/blob/7d519983b983ccc4469064d8b24e7bb125ba361b/rule_shellcheck.go#L165

  • steps.<id>.outputs.json not found for Docker Metadata Action

    steps..outputs.json not found for Docker Metadata Action

    When using the official https://github.com/marketplace/actions/docker-metadata-action as per example. i.e like this: (taken from example in action)

     .....
            name: Login to DockerHub
            if: github.event_name != 'pull_request'
            uses: docker/login-action@v1
            with:
              username: ${{ secrets.DOCKERHUB_USERNAME }}
              password: ${{ secrets.DOCKERHUB_TOKEN }}
          -
            name: Build and push
            uses: docker/build-push-action@v2
            with:
              context: .
              push: ${{ github.event_name != 'pull_request' }}
              tags: ${{ steps.meta.outputs.tags }}
              labels: ${{ steps.meta.outputs.labels }}
              build-args: |
                BUILDTIME=${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.created'] }}
                VERSION=${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.version'] }}
                REVISION=${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.revision'] }}
    

    The output (steps.meta.outputs.json) gets an "property json is not defined", although this way is the working, recommended syntax, see https://github.com/marketplace/actions/docker-metadata-action#json-output-object

  • False Positive for Object Element Dereference

    False Positive for Object Element Dereference

    Actionlint knows about array element dereferences, but not object element dereferences. For example, GitHub Actions supports the syntax ${{ join(needs.*.result, ' ') }} as documented in slack-templates' README despite the fact that needs is not an array. Actionlint 1.6.6 yields an error message of the following form:

    [...]/.github/workflows/test.yaml:[row]:[col]: receiver of array element dereference must be type of array but got "{<job_id_1>: {outputs: {}; result: string}; <job_id_2>: {outputs: {}; result: string}}" [expression]
             |
    [row] |           results: ${{ join(needs.*.result, ' ') }}
    

    Thank you for creating Actionlint! It's quite nice to have a super easy way to check whether an action has syntax errors without wasting build credits.

  • Reusable workflow secrets/inputs/outputs should be case-insensitive

    Reusable workflow secrets/inputs/outputs should be case-insensitive

    When I call a reusable workflow with a secret in the new version of the action (v1.6.18) it fails with

    secret is not defined in reusable workflows error.

    .github/workflows/job.yml:17:11: secret β€œSECRET" is required by "./.github/workflows/reusable-job.yml" reusable workflow [workflow-call]
       |
    17 |     uses: ./.github/workflows/reusable-job.yml
       |           ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    .github/workflows/jobyml:21:7: secret β€œsecret" is not defined in "./.github/workflows/reusable-job.yml" reusable workflow. defined secret is β€œSECRET" [workflow-call]
    

    But when I call it I pass the secrets and still fail.

    If I force the workflow to use the previous version (v1.6.17) without any other change then it succeeds.

    Maybe it's related to the relative path of the reusable workflow.

  • inputs context not supported for workflow_call?

    inputs context not supported for workflow_call?

    I see the same symptoms as described in #152 when referencing inputs from a workflow_call definition. E.g. from this simplified workflow:

    on:
      workflow_call:
        inputs:
          artifact-name:
            description: "Prefix for name of built asset zip file"
            default: build-server
            required: false
            type: string
          build-env:
            description: "Build Environment (dev, test, qa, staging, prod)"
            default: dev
            required: false
            type: string
    
    ...
    
    jobs:
      build:
        name: build-${{ matrix.targetPlatform }}
        concurrency:
          group: ${{ github.workflow }}-${{ inputs.build-env }}-${{ matrix.targetPlatform }}-${{ github.ref }}
          cancel-in-progress: true
        steps:
          - name: Configure AWS credentials
            uses: aws-actions/configure-aws-credentials@v1
            with:
              aws-region: ${{ secrets.aws-region }}
              role-duration-seconds: 14400
              role-session-name: build-${{ matrix.targetPlatform }}-${{ inputs.build-env }}
              role-to-assume: ${{ secrets.aws-role-to-assume }}
    

    Output from actionlint:

    workflow.yml:X:41: property "build-env" is not defined in object type {} [expression]
        |
    X |       group: ${{ github.workflow }}-${{ inputs.build-env }}-${{ matrix.targetPlatform }}-${{ github.ref }}
    
    workflow.yml:Y:82: property "build-env" is not defined in object type {} [expression]
        |
    Y |           role-session-name: build-${{ matrix.targetPlatform }}-${{ inputs.build-env }}
    

    Is it possibly a similar issue? The workflows do work.

    Thank you.

  • Message format for GitHub Actions

    Message format for GitHub Actions

    Hi, thank you so much for creating this nice tool! πŸ˜„

    When I tried running actionlint on GitHub Actions, It took some time to set up. Here is my setup code:

    https://gist.github.com/ybiquitous/c72834b30882d3c1f9c105683d4c1137

    I use Problem Matcher in the setup code, but I doubt it difficult for many users to use Problem Matcher.

    If actionlint could output messages with the ::warning or ::error format of Actions, it might be easier to set up. (But, for the purpose, severities may be needed...)

    For example:

    $ actionlint -format github
    ::error file=.github/workflows/test.yml,line=10,col=15::Something went wrong
    

    What do you think about this idea?

  • set-output is deprecated

    set-output is deprecated

    https://github.blog/changelog/2022-10-11-github-actions-deprecating-save-state-and-set-output-commands/

    I am getting the following warning when running actionlint

    The set-output command is deprecated and will be disabled soon. Please upgrade to using Environment Files. For more information see: https://github.blog/changelog/2022-10-11-github-actions-deprecating-save-state-and-set-output-commands/

  • add stdin-filename support

    add stdin-filename support

    What

    add support for overwriting file names when using stdin.

    Why

    When I tried developing a VSCode extension for actionlint, I felt it would be helpful if we had this option. FYI: https://github.com/fnando/vscode-linter/blob/main/docs/creating-linters.md

    For example, eslint has this kind of option. https://eslint.org/docs/user-guide/command-line-interface#--stdin-filename

    QA

    With --stdin-filename

    shun@Mac-mini actionlint % echo "foo"|  ./actionlint --stdin-filename "test.yml" -
    test.yml:1:1: workflow is scalar node but mapping node is expected [syntax-check]
      |
    1 | foo
      | ^~~
    test.yml:1:1: "jobs" section is missing in workflow [syntax-check]
      |
    1 | foo
      | ^~~
    

    Without --stdin-filename

    shun@Mac-mini actionlint % echo "foo"|  ./actionlint -
    <stdin>:1:1: workflow is scalar node but mapping node is expected [syntax-check]
      |
    1 | foo
      | ^~~
    <stdin>:1:1: "jobs" section is missing in workflow [syntax-check]
      |
    1 | foo
      | ^~~
    
  • Provide a way of ignoring specific shellcheck issues

    Provide a way of ignoring specific shellcheck issues

    Currently shellcheck is executed with the --norc option, which means .shellcheckrc is ignored. There seems to also be no way of specifying custom shellcheck options. I have an use case where I could use the ability to ignore a shellcheck warning.

    This is a simple example of the use case:

    runners_count=$(( ${{ inputs.cypressRunnersCount }} > $MIN_RUNNERS_COUNT ? ${{ inputs.cypressRunnersCount }} : $MIN_RUNNERS_COUNT ))
    

    Using variables in arithmetic expressions causes SC2004 to pop.

    This is just one example of one case however. I would imagine that others will also find cases where they will want to ignore a shellcheck warning.

  • numeric string incorrectly considered a number in a property access

    numeric string incorrectly considered a number in a property access

    When accessing an attribute by indexing using a string object, the linter complains that the string is a number when the string is numeric. This false positive can be avoided by adding non-numeric characters such as in https://github.com/Chia-Network/actions/pull/51/commits/5a9df1b1fbb0e473aeaf9a2feb4231548806627b.

    https://github.com/Chia-Network/actions/actions/runs/3527762746/jobs/5917180165#step:4:76

    ------
    .github/workflows/test-create-venv.yml:22:40: property access of object must be type of string but got "number" [expression]
       |
    22 |     container: ${{ matrix.os.container[matrix.python.matrix] }}
       |                                        ^~~~~~~~~~~~~~~~~~~~~
    .github/workflows/test-create-venv.yml:110:31: property access of object must be type of string but got "number" [expression]
        |
    110 |       if: matrix.os.container[matrix.python.matrix] == ''
        |                               ^~~~~~~~~~~~~~~~~~~~~
    ------
    
  • partial matrix excludes incorrectly flagged as invalid

    partial matrix excludes incorrectly flagged as invalid

    (originally misreported at https://github.com/github/super-linter/issues/3016)

    I regularly have matrix entries with several values such as a string for a name, another string for use in identifying this in other matrix conditional checks etc. I also end up using matrix excludes frequently. GitHub Actions itself lets you exclude based on a partial match such as arch: matrix: arm below while the Actions linter here complains that that is missing the arch: name: ARM value.

    https://github.com/Chia-Network/actions/runs/6812575981?check_suite_focus=true#step:4:75

    2022-06-09 12:35:44 [ERROR]   Found errors in [actionlint] linter!
    2022-06-09 12:35:44 [ERROR]   Error code: 1. Command output:
    ------
    .github/workflows/test-setup-python.yml:55:15: value {"matrix": "windows"} in "exclude" does not exist in matrix "os" combinations. possible values are {"name": "macOS", "matrix": "macos", "runs-on": {"arm": ["m1"], "intel": ["macos-latest"]}}, {"name": "Ubuntu", "matrix": "ubuntu", "runs-on": {"arm": ["Linux", "ARM64"], "intel": ["ubuntu-latest"]}}, {"name": "Windows", "matrix": "windows", "runs-on": {"intel": ["windows-latest"]}} [matrix]
       |
    55 |               matrix: windows
       |               ^~~~~~~
    .github/workflows/test-setup-python.yml:57:15: value {"matrix": "arm"} in "exclude" does not exist in matrix "arch" combinations. possible values are {"matrix": "arm", "name": "ARM"}, {"name": "Intel", "matrix": "intel"} [matrix]
       |
    57 |               matrix: arm
       |               ^~~~~~~
    ------
    

    https://github.com/Chia-Network/actions/blob/4146f33b66d7a011e5429edcf900a3c4f0c61a95/.github/workflows/test-setup-python.yml#L19-L57

          matrix:
            os:
              - name: macOS
                matrix: macos
                runs-on:
                  arm: [m1]
                  intel: [macos-latest]
              - name: Ubuntu
                matrix: ubuntu
                runs-on:
                  arm: [Linux, ARM64]
                  intel: [ubuntu-latest]
              - name: Windows
                matrix: windows
                runs-on:
                  intel: [windows-latest]
            python:
              - name: '3.7'
                action: '3.7'
                check: '3.7'
              - name: '3.8'
                action: '3.8'
                check: '3.8'
              - name: '3.9'
                action: '3.9'
                check: '3.9'
              - name: '3.10'
                action: '3.10'
                check: '3.10'
            arch:
              - name: ARM
                matrix: arm
              - name: Intel
                matrix: intel
            exclude:
              - os:
                  matrix: windows
                arch:
                  matrix: arm
    
  • Feature request: Flag when the gh cli is used without providing GITHUB_TOKEN

    Feature request: Flag when the gh cli is used without providing GITHUB_TOKEN

    Problem

    As it stands, you can use the gh cli in GitHub workflows, but you have to provide the GITHUB_TOKEN via an environment variable.

    Solution

    Flag a lint warning whenever the gh cli is used but the GITHUB_TOKEN is not provided.

  • Feature request: add linter for typescript

    Feature request: add linter for typescript

    actionlint uses external linters for bash verification (shellcheck) and python verification (pyflakes). It would be great if actionlint can also lint typescript code in workflows. This will be especially useful, because github provides a standard actions/github-script action which allows you to write typescript code inside the workflow. Also, native API for GitHub is provided in typescript, so writing small typescript snippets is handy.

    Small snippets, like create a pull request or adding a comment to an issue, are easier to write inside the workflow. It would be great if actionlint can lint this code for at least basic typescript syntax, like "missing commas", "matching brackets", etc.

  • Workflows not in repository root are not detected

    Workflows not in repository root are not detected

    actionlint can't find workflows in the non-root folder of the repository if there is no .git file or folder next to .github folder.

    How to reproduce: I have a repository with the following structure:

    /
    β”œβ”€ .github/
    β”‚  β”œβ”€ workflows/
    β”‚  β”‚  β”œβ”€ *.yaml workflows for this repository
    β”œβ”€ template1/
    β”‚  β”œβ”€ .github/
    β”‚  β”‚  β”œβ”€ workflows/
    β”‚  β”‚  β”‚  β”œβ”€ *.yaml workflows for template1
    β”œβ”€ template2/
       β”œβ”€ github/
          β”œβ”€ workflows/
             β”œβ”€ *.yaml workflows for template2
    

    I use this repository to automatically generate workflows for newly created repositories in my organization (template1 can be "python project" and template2 can be "C++ project").

    I can easily lint workflows in .github/workflows in the root of the repository (using GitHub Acitons):

    - run: actionlint
    

    I also want to lint workflows in the templates folder, like:

    - run: actionlint
      working-directory: template1
    - run: actionlint
      working-directory: template2
    

    but this doesn't work. actionlint simply doesn't detect template1 and template2 as "projects", because of this check: https://github.com/rhysd/actionlint/blob/acc6e8cd8e2c25c2220da68465a968c499bd1207/project.go#L29-L32 This seems like a bug.

    Workarounds:

    1. automatically create (and then remove) an empty .git file next to .github folder;
    2. use find to find workflows I need, cat them and pass to actionlint.

    I can workaround the issue, but it would be great if it is fixed in the code.

GitHub Actions demo for a monorepo Go project

GitHub Actions demo for a monorepo Go project The purpose of this repository is to demonstrate using a GitHub action as a pull request status check in

Oct 31, 2021
Explores GitHub Actions in Go Lab from GopherCon 2021

Gopher A Tweet An action that tweets. Gopher A Tweet was created based on GopherCon 2021s Gophers of Microsoft: GitHub Action in Go Lab to explore bui

Dec 10, 2021
Golang-action - A template repository for writing custom GitHub Actions in Golang

Golang Action A template repository for writing custom GitHub Actions in Golang.

Feb 12, 2022
Google Maps API checker
Google Maps API checker

GAP Google API checker. Based on the study Unauthorized Google Maps API Key Usage Cases, and Why You Need to Care and Google Maps API (Not the Key) Bu

Nov 17, 2022
Package githubv4 is a client library for accessing GitHub GraphQL API v4 (https://developer.github.com/v4/).

githubv4 Package githubv4 is a client library for accessing GitHub GraphQL API v4 (https://docs.github.com/en/graphql). If you're looking for a client

Dec 26, 2022
May 25, 2021
A GitHub CLI extension that displays collaboration-related information about a GitHub repository.
A GitHub CLI extension that displays collaboration-related information about a GitHub repository.

collab-scanner GitHub CLI extension A GitHub CLI extension that displays collaboration-related information on a repository. Install gh extension insta

Dec 30, 2022
Startpage - Lambda for reading rss feeds and generating a minimal start page for a static site

startpage generate a startpage of links for a static site hosted via AWS What It

Sep 6, 2022
Go library for accessing the GitHub API

go-github go-github is a Go client library for accessing the GitHub API v3. Currently, go-github requires Go version 1.9 or greater. go-github tracks

Dec 30, 2022
Go library for accessing trending repositories and developers at Github.
Go library for accessing trending repositories and developers at Github.

go-trending A package to retrieve trending repositories and developers from Github written in golang. This package were inspired by rochefort/git-tren

Dec 21, 2022
:fishing_pole_and_fish: Webhook receiver for GitHub, Bitbucket, GitLab, Gogs

Library webhooks Library webhooks allows for easy receiving and parsing of GitHub, Bitbucket and GitLab Webhook Events Features: Parses the entire pay

Jan 4, 2023
Periodically collect data about my Twitter account and check in to github to preserve an audit trail.

Twitter audit trail backup This repository backs up my follower list, following list, blocked accounts list and muted accounts list periodically using

Dec 28, 2022
Easily manage your github credentials
Easily manage your github credentials

HUB ADMIN Hub Admin is a command-line tool managing your github credentials Installation go get github.com/crewdevio/HubAdmin How to use Open he

Oct 20, 2021
A demo repo to show KICS Github Action in Action

?? KICS GitHub Actions Demo This repository shows how KICS GitHub Action can be set and was fully inspired by the documentation on KICS GitHub Actions

Nov 23, 2021
A Github action to post to news.ycombinator.com

action-hackernews-post Unofficial A Github action to post to news.ycombinator.com Uses @lukakerr's hkn go module to login and post to HN Guidelines As

Mar 12, 2022
A starting point for a GitHub Action based in Go.

GitHub Action Using Go This is a starting point for a GitHub Action based in Go. This repo provides all the structure needed to build a robust GitHub

Dec 11, 2021
A GitHub action for the Go! programming language (by Francis McCabe, 2004)

Setup Go! (GitHub Action) This project is a GitHub action for the Go! programmin

Oct 22, 2022
A Github Action to auto approve pull requests that contain only document reviews.

Approve documentation review A Github Action to auto approve pull requests that contain only document reviews. The Cloud Platform team have a document

Dec 23, 2021
GitHub Utilities for managing classroom repositories.

ghutil GitHub Utilities for bulk operations. Requirements A ghutil.toml configuration file is needed in the working directory. It should have entries

Dec 21, 2021