Recent example: M130 cron pipeline (PRs #1591..#1599) — 9 PRs over 3 hours.
Two real bugs caused most of it:
cat <<BODY with body at column 1 silently broke workflow registration. actionlint output: could not parse as YAML: could not find expected ':'.if: ${{ secrets.X != '' }} in a step. actionlint: context "secrets" is not allowed here.Both would have been caught in 30 seconds before the first push.
| Layer | When it runs | Speed | Coverage |
|---|---|---|---|
| Pre-commit hook | Before commit lands locally | ~50ms | Per-developer (only those with actionlint installed) |
| CI workflow | On every PR + push to main | ~30s | Everyone — required check |
Extends bin/git-hooks/pre-commit (the repo's existing hook, runs via core.hooksPath = bin/git-hooks) with Section 8:
WORKFLOW_FILES=$(echo "$STAGED_FILES" | grep -E '^\.github/workflows/.*\.ya?ml$' || true)
if [[ -n "$WORKFLOW_FILES" ]]; then
echo -n " Linting GitHub Actions workflows... "
if command -v actionlint >/dev/null 2>&1; then
if ! actionlint -shellcheck= $WORKFLOW_FILES >/tmp/actionlint-precommit.log 2>&1; then
echo "FAILED"
head -20 /tmp/actionlint-precommit.log
ERRORS=$((ERRORS + 1))
else
echo "OK"
fi
else
echo "SKIP (actionlint not installed — install: brew install actionlint)"
fi
fi
Graceful fallback: missing actionlint → warn + skip, not block.
validate-workflows.ymlname: Validate Workflows
on:
pull_request:
paths: ['.github/workflows/**']
push:
branches: [main]
paths: ['.github/workflows/**']
jobs:
actionlint:
runs-on: ubuntu-latest
timeout-minutes: 3
steps:
- uses: actions/checkout@de0fac2e... # v6.0.2
with:
persist-credentials: false
- run: |
curl -sSfL https://.../download-actionlint.bash -o /tmp/install.sh
bash /tmp/install.sh 1.7.7
./actionlint -version
- run: ./actionlint -shellcheck= .github/workflows/*.yml
-shellcheck= (empty)Diagnostic on current main: 0 GHA-native errors ([expression], [syntax-check], [deprecated], [action]) but 69 shellcheck info/style findings across 12 workflows.
Most are SC2086 (unquoted vars in ${{ ... }} GH expressions) which are pre-quoted by GHA and harmless in practice. Enabling shellcheck would block every PR until those 69 are hand-fixed.
Strategy: enable actionlint NOW with shellcheck off — catch the dangerous bugs immediately. Re-enable shellcheck later as a hardening step (separate backlog ticket).
Validate Workflows / actionlint CI check fails.