
GitHub Actions: Lint and Fix Your Workflow YAML
Broken YAML, missing `on` or `jobs`, empty steps: structurally validate workflows before you push — and keep actionlint in CI.
Why check a workflow before you merge to main?
A .github/workflows/*.yml file that does not parse, or a job with no runs-on, often shows up only after you push: red check, GitHub logs, blocked review. FastMinify does not ship actionlint. The GitHub Actions validator runs structural checks in the browser — on, jobs, steps with run or uses — and the GitHub Actions formatter reindents YAML (2-space convention). The full cluster lives on the CI/CD tools hub. To automate JS/CSS minification in the pipeline, see the CI/CD minification guide.
Workflow anatomy: what FastMinify checks (and what it does not)
A workflow is a single YAML document. GitHub expects an on trigger and a jobs mapping. Each job needs a runner (runs-on), a reusable workflow (uses), or a steps list. Each step must define run or uses.
---) are rejecteduses without steps is accepted; nested YAML is not deeply inspectedjobs map or empty steps list yields a warning, not always a blocking erroron triggers after a successful parseFastMinify exposes two tools. Neither is actionlint. Mixing the three up produces workflows that look “valid” and still fail in CI.
Before
After
js-yaml round-trip, 2-space indent (GitHub convention) — see format-github-actionson / jobs / step shape — see validate-github-actions${{ }} expressions, permissions, IDs, action lookup — run in CI, not in FastMinifyactions/checkout@v4 exists or that an expression is safeInterpolating untrusted context (github.event.issue.title, a PR label) straight into run: is a classic script-injection pattern. FastMinify does not evaluate expressions and does not flag that motif.
env:, not by interpolating them into the scriptpermissions: at the minimum (contents, pull-requests)run or uses — even if the script is unsafeValidate, then format: a concrete workflow
Open the GitHub Actions validator, paste a single workflow file (max 512 KiB) and wait for debounce (~300 ms). The panel shows a verdict, issues with a line hint on parse errors, and job/step/trigger counts. Empty input stays idle — not “invalid”.
The GitHub Actions formatter pretty-prints via js-yaml. Paste, upload, or sample: auto-format; after a manual edit, use the Format button (⌘↵). UI indent follows the 2-space convention. This is not a semantic rewriter.
A teammate added - name: empty step with no script. CI fails late, with a GitHub message that is hard to read in the PR overlay.
Step 1: paste the workflow into validate-github-actions
Open validate-github-actions. An invalid verdict such as “step needs run or uses” points at the empty step.
Step 2: add run or uses
Replace the step with uses: actions/checkout@v4 or a real run:. Re-paste until the panel shows a valid structure.
Step 3: format and open the PR
Send the fixed YAML through format-github-actions, copy, commit. Reviewers read indented jobs, not a blob.
An example copied from GitHub docs or a gist arrives with mixed spaces. You can no longer see where a job ends.
Step 1: format first if you cannot even read the file
Paste into format-github-actions. This step does not prove the workflow is structurally complete.
Step 2: validate the indented document
Copy the output into the validator. Check on, jobs, and that every step has an action.
Step 3: keep actionlint for CI
Once the shape is OK, expressions / IDs / marketplace stay in the pipeline — FastMinify does not replace that gate.
Keep actionlint (and minify) in CI
The browser is the fast loop before git push. Teams still keep actionlint as a merge gate for ${{ }} expressions and rules FastMinify does not implement. The snippet follows the official actionlint download script: pin a version in your repo rather than a floating main. To minify JS/CSS in the same pipeline, see the CI/CD minification guide.
Basic example
If the repo is on GitLab, the counterparts are format-gitlab-ci and validate-gitlab-ci — same idea (YAML plus structure, not a runner). Dockerfile and Compose stay on the DevOps hub: lint a Dockerfile, validate Compose and .env.
Conclusion
Paste the workflow, fix the structure (on, jobs, steps), reindent, then push. FastMinify runs that loop locally and never uploads the YAML. It is not actionlint: expressions, advanced permissions, and whether an action exists stay in CI and review. For generic YAML, stay on beautify-yaml; for GitLab, on the same CI/CD hub.
Related Articles

SDL syntax errors, schema review and formatting before merge — extends the existing GraphQL beautifier.

Lower your OpenAI/Anthropic/Gemini bill: estimate input/output, model batch and caching in your calculations — verified rates, 100% local.

RAG, multi-turn agents, system prompts: calculate context-window usage and remaining headroom before calling the API.