ci: gate GitHub workflows with pinned actionlint - #2517
Merged
Conversation
A self-broken ci.yml cannot report its own breakage, so parse every workflow in the local lint path that no-mistakes already runs.
This was referenced Aug 17, 2026
A self-broken ci.yml still has to fail in the local lint path, and the named tool for that gate is actionlint, not a new Ruby runtime.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Intent
Rework existing open PR #2517 (branch fm/fm-ci-actionlint-gate-r1) so GitHub workflow lint uses pinned actionlint instead of Ruby/Psych. This is firstmate shared tracked material (bin/, CONTRIBUTING.md, the firstmate-coding-guidelines skill, .github/workflows). Update that existing PR in place; do not open a second PR. The captain owns the merge.
Replace the Ruby/Psych implementation in bin/fm-lint-workflows.sh with a call to actionlint (the Go static binary). Install and version-pin actionlint the same way firstmate already requires shellcheck: pin one exact version and refuse any other; add bin/fm-install-actionlint.sh that fetches a pinned release and verifies the download with a checksum; install actionlint in .github/workflows/ci.yml's lint job the same way shellcheck is installed; document the actionlint pin in CONTRIBUTING.md alongside the shellcheck one.
Hard requirements that must all hold:
(a) KEY REQUIREMENT: a self-broken .github/workflows/ci.yml must be caught in the LOCAL / no-mistakes lint lane (bin/fm-lint.sh fails locally) BEFORE merge, because a self-broken ci.yml cannot report its own breakage in CI.
(b) Validate ALL .github/workflows/*.yml, not just ci.yml.
(c) Keep the #2512-class malformed-workflow regression (the column-0 heredoc inside a run: | block that produces invalid workflow YAML) in tests/fm-lint-workflows.test.sh. Keep the repro and that it FAILS the gate; the assertion may match actionlint's actual failure output instead of the old Ruby "invalid YAML" string. Tests must exercise the real bin/fm-lint.sh / bin/fm-lint-workflows.sh executable path, never source bytes.
(d) Add NO Ruby dependency and NO npm-package dependency. actionlint is a single static Go binary. bin/fm-lint-workflows.sh must no longer reference ruby/psych anywhere.
In the firstmate-coding-guidelines skill, add a short durable note (one-owner rule; patch existing language) stating: when a task names a specific tool, implement it with that tool, or explicitly flag the substitution and its new dependency footprint for review before shipping.
This changes firstmate's instruction surface (the coding-guidelines skill and CONTRIBUTING), so the PR should note that running homes pick it up after merge plus a firstmate self-update, and that landing timing is coordinated with the main firstmate.
Accepted implementation choices: pin actionlint 1.7.12; fm-lint-workflows.sh --required-version owns that pin and the installer reads it; refuse any other installed actionlint version, matching the shellcheck refuse-on-mismatch pattern; disable actionlint's extra shell and Python subprocess linters so this gate is the named workflow linter, not a second shell lint of run: blocks (bin/fm-lint.sh already owns ShellCheck of the canonical shell set).
What Changed
Risk Assessment
✅ Low: The change is well-bounded, follows the existing pinned-tool pattern, validates all workflow YAML through the local canonical lint path, and introduces no substantiated source defects.
Testing
Confirmed the pinned actionlint and ShellCheck environment, ran the two focused lint regression scripts, manually demonstrated all repository workflows passing and a self-broken
ci.ymlfailing through the actual localbin/fm-lint.shpath, and verified that the installer rejects a tampered archive; all expected behavior succeeded.Evidence: Pinned actionlint validation and self-broken local CI gate transcript
Source: Pinned actionlint validation and self-broken local CI gate transcript
Pipeline
Updates from git push no-mistakes
✅ **intent** - passed
✅ No issues found.
✅ **Rebase** - passed
✅ No issues found.
✅ **Review** - passed
✅ No issues found.
🔧 **Test** - 1 issue found → auto-fixed ✅
bin/fm-test-run.sh tests/fm-lint-workflows.test.shbin/fm-test-run.sh tests/fm-lint.test.shExecuted copiedbin/fm-lint.shandbin/fm-lint-workflows.shthrough their no-argument public path against two valid workflows, then a #2512-class self-brokenci.yml, using real actionlint 1.7.12gh pr view 2517 --json number,headRefName,title,body,urlgit status --shortafter evidence generation🔧 Fix: PR description fix blocked by remote-write boundary
✅ Re-checked - no issues remain.
command -v actionlint; actionlint -version; command -v shellcheck; shellcheck --versionconfirmed the required local tool pins were available.tests/fm-lint-workflows.test.shexercised pinned-version enforcement, all-workflow discovery, malformed YAML rejection, installer retry behavior, and the localfm-lint.shregression path.tests/fm-lint.test.shexercised the canonical local lint owner, including workflow validation when no shell targets changed.bin/fm-lint-workflows.shvalidated all three repository workflow files with actionlint 1.7.12.Ranbin/fm-lint.shagainst a fixture repository containing the #2512-class column-zero heredoc in.github/workflows/ci.yml; the local/default lane reported the actionlint parse error and exited 1.Ranbin/fm-install-actionlint.shwith a deliberately tampered downloaded archive; it rejected the checksum and did not install a binary..no-mistakes.yaml:43- store_in_repo: true contradicts CONTRIBUTING.md and docs/configuration.md, which require test evidence outside the repository; resolving this requires a non-documentation configuration change.🔧 Fix: Review actionlint docs; PR update remains
1 warning still open:
✅ **Lint** - passed
✅ No issues found.
✅ **Push** - passed
✅ No issues found.