chore: replace yq-based workflow validation with actionlint#381
Conversation
f6eea09 to
eafbbb5
Compare
There was a problem hiding this comment.
Review: Approved ✅
This PR makes an excellent improvement to workflow validation by replacing a custom 227-line yq-based implementation with actionlint, a purpose-built static analysis tool.
No Blocking Issues
This is a CI workflow change, not application code, so the TypeScript requirements from CLAUDE.md (strict mode, named exports, unit tests, license headers) don't apply.
What's Good
-
Better tooling: actionlint provides comprehensive validation that the custom script couldn't match:
- Type-checking of
${{ }}expressions - Action input/output validation against action.yml schemas
- Job dependency validation
- Integration with shellcheck for
run:steps
- Type-checking of
-
Reduced maintenance: 227 lines → 30 lines with better coverage
-
Removed false positives: The old script warned about legitimate patterns (e.g., having both
pushandpull_requesttriggers) -
Security: Uses the official download script from rhysd's repo rather than a third-party wrapper action, minimizing supply chain risk. The workflow appropriately has
contents: readpermissions only. -
Added
workflow_dispatch: Enables manual testing of the validation workflow itself
Minor Notes (Non-blocking)
- The
bash <(curl ...)pattern is standard for actionlint installation and documented in their official usage guide - The download script correctly sets the
executableoutput that the next step references
Well-documented PR with clear rationale. Ship it!
This PR replaces the hand-rolled yq-based workflow validation with actionlint (https://github.com/rhysd/actionlint), a proper static analysis tool for GitHub Actions workflows. Why the old approach was insufficient The previous workflow-validation.yml (227 lines) performed shallow checks using yq and grep: - YAML syntax validation: redundant, because GitHub already validates workflow YAML natively when you push. If a workflow has invalid YAML, GitHub surfaces the error immediately without needing a separate CI job. - Structural field checks (name, on, jobs, runs-on, steps): these are the most basic possible checks. They don't validate that the values are correct, just that the keys exist. - Deprecated action version warnings: only checked for actions/checkout@v1/v2 and actions/setup-node@v1/v2. Easy to miss other stale actions. - Noisy/incorrect warnings: warned about workflows that have both push and pull_request triggers, which is a completely normal and common pattern. Also warned that workflow_dispatch was absent, which is advisory at best. The old workflow itself had both push and pull_request triggers, so it would warn about itself. - Superficial secret detection: the grep patterns used were easy to bypass and produced false positives. Why actionlint is better actionlint is a purpose-built static checker for GitHub Actions workflows. It provides: - Type-checking of ${{ }} expressions: catches invalid context references, wrong types, undefined variables - Action input/output validation: verifies that inputs passed to actions actually exist and are the correct type, based on each action's action.yml schema - needs dependency validation: catches references to jobs that don't exist - Runner label validation: flags invalid or unknown runner labels - Shell script analysis: integrates with shellcheck to lint run: steps - Glob syntax validation - Cron expression validation What changed - Removed all yq-based validation steps - Added two steps: download actionlint using rhysd's official download script, then run it - Added workflow_dispatch trigger for manual runs - 227 lines to 30 lines Used rhysd's recommended download script approach rather than a third-party action wrapper, to avoid a dependency on an external maintainer keeping a wrapper action up to date.
eafbbb5 to
a5d75e4
Compare
There was a problem hiding this comment.
Review Summary
This PR replaces 227 lines of hand-rolled yq-based workflow validation with actionlint, a purpose-built static analyzer for GitHub Actions. This is a clear improvement.
Blocking Issues
None.
Observations
Security: The approach uses bash <(curl ...) to download actionlint, which is the officially documented method. While this pattern has inherent supply-chain considerations, the permissions are properly locked down to contents: read, and this is the maintainer-recommended approach vs. depending on third-party wrapper actions.
Code Quality: The change reduces complexity from 227 lines to 30 lines while gaining significantly more robust validation (expression type-checking, action input/output validation, needs dependency validation, shellcheck integration, etc.).
Rationale: The PR description clearly explains why the old approach was insufficient:
- Redundant YAML validation (GitHub already does this)
- Shallow structural checks that only verified key existence
- Limited deprecated action detection
- False positive warnings (e.g., warning about
push+pull_requesttriggers which is a common pattern) - Superficial secret detection with easy bypasses
LGTM!
There was a problem hiding this comment.
Review Summary
This PR is a good improvement that replaces 227 lines of hand-rolled yq-based validation with actionlint, a purpose-built static analyzer for GitHub Actions workflows. The changes are well-motivated and well-documented in the PR description.
No blocking issues found.
What was reviewed
.github/workflows/workflow-validation.yml
- ✅ Correctly replaces verbose manual validation with
actionlint - ✅ Adds
workflow_dispatchfor manual testing - ✅ Uses official actionlint download script from rhysd's repository
.github/workflows/issue-planner.yml
- ✅ Shell quoting fixes (
$GITHUB_OUTPUT→"$GITHUB_OUTPUT") follow best practices - ✅ Grouped command for multi-line heredoc is cleaner and more correct
Suggestions (non-blocking)
-
Consider pinning actionlint download script: Currently downloads from
mainbranch. For maximum security, you could pin to a specific commit hash. However, the current approach is the officially recommended method and is acceptable. -
Minor: The download step could potentially fail silently if the curl fails. Consider adding
set -euo pipefailor checking the exit code. This is not critical since the subsequentRun actionlintstep would fail anyway.
Notes
- DDD review: N/A - these are CI workflow files, not domain code
- Test coverage: N/A - workflow changes are tested by CI itself
- CLAUDE.md compliance: N/A - no TypeScript code modified
LGTM! 🚀
Summary
This PR replaces the hand-rolled
yq-based workflow validation withactionlint, a proper static analysis tool for GitHub Actions workflows.Why the old approach was insufficient
The previous
workflow-validation.yml(227 lines) performed shallow checks usingyqandgrep:name,on,jobs,runs-on,steps) — these are the most basic possible checks. They don't validate that the values are correct, just that the keys exist.actions/checkout@v1/v2andactions/setup-node@v1/v2. Easy to miss other stale actions.pushandpull_requesttriggers, which is a completely normal and common pattern. Also warned thatworkflow_dispatchwas absent, which is advisory at best. The old workflow itself had bothpushandpull_requesttriggers, so it would warn about itself.Why actionlint is better
actionlintis a purpose-built static checker for GitHub Actions workflows. It provides:${{ }}expressions — catches invalid context references, wrong types, undefined variablesaction.ymlschemaneedsdependency validation — catches references to jobs that don't existshellcheckto lintrun:stepsWhat changed
yq-based validation stepsactionlintusing rhysd's official download script, then run itworkflow_dispatchtrigger for manual runsApproach
Used rhysd's recommended download script approach rather than a third-party action wrapper, to avoid a dependency on an external maintainer keeping a wrapper action up to date.
🤖 Generated with Claude Code