Skip to content

ci: gate GitHub workflows with pinned actionlint - #2517

Merged
kunchenguid merged 3 commits into
mainfrom
fm/fm-ci-actionlint-gate-r1
Aug 17, 2026
Merged

ci: gate GitHub workflows with pinned actionlint#2517
kunchenguid merged 3 commits into
mainfrom
fm/fm-ci-actionlint-gate-r1

Conversation

@kunchenguid

@kunchenguid kunchenguid commented Aug 17, 2026

Copy link
Copy Markdown
Owner

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

  • Add a checksum-verified installer for actionlint 1.7.12 and install the pinned binary in CI.
  • Extend the canonical local/no-mistakes lint path to validate every GitHub workflow, reject mismatched actionlint versions, and disable its secondary shell and Python linters.
  • Add executable-path regression coverage for malformed workflows, including the fix(bin): emit a real @AGENTS.md pointer instead of a CLAUDE.md symlink #2512 heredoc case, and update contributor guidance and the named-tool rule. Running homes pick up the instruction changes after merge and a firstmate self-update, coordinated with the main firstmate.

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.yml failing through the actual local bin/fm-lint.sh path, 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

$ bin/fm-lint-workflows.sh
fm-lint-workflows.sh: actionlint 1.7.12 (pinned 1.7.12)
fm-lint-workflows.sh: 3 workflow files valid

Discovered workflow files:
.github/workflows/ci.yml
.github/workflows/no-mistakes-required.yml
.github/workflows/windows-herdr-spike.yml

$ bin/fm-lint.sh  # local/no-mistakes default lane against self-broken ci.yml
fm-lint.sh: ShellCheck 0.11.0 (pinned 0.11.0)
fm-lint.sh: no changed lint targets
fm-lint-workflows.sh: actionlint 1.7.12 (pinned 1.7.12)
.github/workflows/ci.yml:10:0: could not parse as YAML: could not find expected ':' [syntax-check]
   |
10 | EOF
   | 
exit status: 1
- Outcome: 🔧 1 issue found → auto-fixed ✅ across 2 runs (3m25s)

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 ✅
  • 🚨 PR ci: gate GitHub workflows with pinned actionlint #2517's description still claims the implementation uses Ruby/Psych, includes Ruby-specific test results and evidence, and omits the required note that running homes receive the instruction-surface changes after merge plus a firstmate self-update coordinated with main firstmate. Update the existing PR description in place.
  • bin/fm-test-run.sh tests/fm-lint-workflows.test.sh
  • bin/fm-test-run.sh tests/fm-lint.test.sh
  • Executed copied bin/fm-lint.sh and bin/fm-lint-workflows.sh through their no-argument public path against two valid workflows, then a #2512-class self-broken ci.yml, using real actionlint 1.7.12
  • gh pr view 2517 --json number,headRefName,title,body,url
  • git status --short after 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 --version confirmed the required local tool pins were available.
  • tests/fm-lint-workflows.test.sh exercised pinned-version enforcement, all-workflow discovery, malformed YAML rejection, installer retry behavior, and the local fm-lint.sh regression path.
  • tests/fm-lint.test.sh exercised the canonical local lint owner, including workflow validation when no shell targets changed.
  • bin/fm-lint-workflows.sh validated all three repository workflow files with actionlint 1.7.12.
  • Ran bin/fm-lint.sh against 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.
  • Ran bin/fm-install-actionlint.sh with a deliberately tampered downloaded archive; it rejected the checksum and did not install a binary.
⚠️ **Document** - 1 warning
  • ⚠️ .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.
  • ℹ️ PR ci: gate GitHub workflows with pinned actionlint #2517 still needs the required delivery note that running homes receive this after merge and firstmate self-update, with landing coordinated with the main firstmate.

🔧 Fix: Review actionlint docs; PR update remains
1 warning still open:

✅ **Lint** - passed

✅ No issues found.

✅ **Push** - passed

✅ No issues found.

A self-broken ci.yml cannot report its own breakage, so parse every
workflow in the local lint path that no-mistakes already runs.
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.
@kunchenguid kunchenguid changed the title ci(lint): validate GitHub workflow YAML in the local lint gate ci: gate GitHub workflows with pinned actionlint Aug 17, 2026
@kunchenguid
kunchenguid merged commit 0ae14af into main Aug 17, 2026
14 checks passed
@kunchenguid
kunchenguid deleted the fm/fm-ci-actionlint-gate-r1 branch August 17, 2026 20:34
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