Skip to content

Actionlint and ShellCheck: catch CI errors before they run

CI/CD · GitHub Actions · Shell
Not studied

Added 2026-10-11. Requires actionlint, ShellCheck and pre-commit to be installed; pin the versions in the developer environment and in CI.

Problem

A plain YAML linter doesn't notice an invalid GitHub Actions key or a reference to a non-existent matrix variable. A shell script passes a simple syntax check but breaks on a path with spaces.

Solution

actionlint is "a static checker for GitHub Actions workflow files": a syntax check against the workflow syntax, a strong type check for ${{ }} expressions, checks of action inputs/outputs, and security checks for script injection by untrusted inputs and hard-coded credentials. It integrates with ShellCheck for scripts at run:.

ShellCheck is "a shell script static analysis tool" that "gives warnings and suggestions for bash/sh shell scripts". It needs a separate run for script files: the contents of a called ./scripts/deploy.sh do not automatically become part of the workflow check.

Example

Add to the existing repos list in .pre-commit-config.yaml:

repos:
  - repo: local
    hooks:
      - id: actionlint
        name: Check GitHub Actions
        entry: actionlint
        language: system
        files: '^\.github/workflows/.*\.ya?ml$'
      - id: shellcheck
        name: Check shell scripts
        entry: shellcheck
        language: system
        types: [shell]
pre-commit run actionlint --all-files
pre-commit run shellcheck --all-files

Without pre-commit, check the workflows and a specific script directly:

actionlint
shellcheck scripts/deploy.sh

An expression in a workflow step: event data is passed through an environment variable so the text never becomes shell source code.

- name: Show PR title
  env:
    PR_TITLE: ${{ github.event.pull_request.title }}
  run: printf '%s\n' "$PR_TITLE"

This is a steps fragment, not a full workflow. If the project has no GitHub Actions, keep ShellCheck for scripts: actionlint does not check GitLab CI. Write linter exceptions for a specific case with an explanation, without disabling checks globally.

When to use

When changing workflows, backup and deploy scripts. Complements pre-commit for IaC.

Source

How to apply it yourself

  1. Check all tracked workflows and shell files, work through the current findings.
  2. Wire the same checks into CI so the local hook can't be bypassed by accident.
  3. In a test branch, add an unknown workflow key and an unquoted variable with spaces; make sure the checks catch the errors.
  4. After the fix and a green CI, mark it "Studied".