Skip to content

ci: lint workflow files with actionlint - #234

Merged
Bccorb merged 1 commit into
mainfrom
ci/actionlint
Aug 31, 2026
Merged

ci: lint workflow files with actionlint#234
Bccorb merged 1 commit into
mainfrom
ci/actionlint

Conversation

@Bccorb

@Bccorb Bccorb commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

Adds a Lint workflows job to ci.yml that runs actionlint over all five workflow files on every pull request.

Why

A pull request only runs ci.yml and codeql.yml. release.yml and docker-publish.yml are not read by anything until they run for real, on a tag push or a push to main. That is how a broken release gate reaches main with every check green, which is exactly what #200 would have done. This is the one check that parses those two files while they can still be fixed cheaply.

What it does and does not catch

Measured against deliberate regressions rather than assumed:

Mutation Caught
Reference to an undefined step id yes, property "typo_id" is not defined in object type ...
Malformed expression, unbalanced [ yes, unexpected end of input while parsing closing bracket
Misspelled input to a third-party action (fetch-dept) no
The changesets v2 output rename from #233 no

Worth being plain about the last row: actionlint types a step's outputs as {string => string}, so any output name passes and this would not have caught the bug that motivated it. It narrows the gap rather than closing it. What it does cover is workflow schema, expression syntax, context and needs: references, runner labels, and, because shellcheck is preinstalled on the runner, the shell in every run: block.

Install method

Pinned by version and sha256:

ACTIONLINT_VERSION: 1.7.12
ACTIONLINT_SHA256: 8aca8db96f1b94770f1b0d72b6dddcb1ebb8123cb3712530b08cc387b349a3d8

Upstream recommends bash <(curl .../main/scripts/download-actionlint.bash), which pipes an unpinned mutable ref into bash. That is not a reasonable thing to run in the CI of an auth server that signs its own images. A third-party wrapper action would be Dependabot-trackable but adds another actor to the supply chain, and uses: docker:// is explicitly not tracked by Dependabot either, so the tracking argument does not survive.

The tradeoff is that neither form is Dependabot-tracked, so the version and checksum are bumped by hand. The checksum is the point: a swapped artifact fails the job loudly instead of executing.

Verification

  • Checksum verified independently against the release rather than copied from the checksums file alone
  • The install and lint steps were executed end to end in ubuntu:24.04 with shellcheck present: checksum OK, lint exit 0
  • actionlint passes on its own new job
  • Full local gate green via the pre-commit hook: lint, format, typecheck, 1194 tests, coverage, build

A pull request only runs ci.yml and codeql.yml. Nothing reads release.yml or
docker-publish.yml until they run for real on a tag or a push to main, which is
how a broken release gate would reach main with every check green. This adds the
one job that parses all five workflows on every pull request.

Verified against deliberate regressions: it catches an undefined step id and
malformed expression syntax, and shellcheck is preinstalled on the runner so it
also covers the shell in every run: block. It does not validate inputs or
outputs of third party actions, so it would not have caught the changesets v2
output rename in #233. This narrows the gap rather than closing it.

actionlint is pinned by version and sha256 rather than installed through the
upstream download script, which pipes an unpinned ref into bash. Dependabot
tracks neither that script nor a docker:// reference, so the pair is bumped by
hand and the checksum makes a swapped artifact fail loudly.
@Bccorb
Bccorb merged commit 2188754 into main Aug 31, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant