Skip to main content
RunBook Academy

← All break/fix scenarios in Git, CI/CD & GitOps

intermediateci-workflow~25 min

Invalid CI workflow YAML

Reported symptoms

  • GitHub Actions tab shows a red X for every push since a specific commit, with status "Invalid workflow file"
  • The error tooltip reads "We could not parse this YAML, possible trace: mapping values are not allowed here" or "did not find expected `-` indicator"
  • The error message points at a specific line and column in `.github/workflows/<name>.yml`
  • Other workflows in the same directory parse fine and execute normally
  • `cat -A .github/workflows/<name>.yml | head -20` shows `^I` characters where spaces should be (literal tab characters)
  • The PR that introduced the file is open and the workflow never executes; the PR is not blocked by it
  • `gh workflow list` shows the workflow in the list, but `gh workflow view <name>` returns "could not parse"

Evidence

  • · `gh workflow view deploy.yml` returns `Error: workflow file failed to parse` and prints the parser error with file/line/column
  • · `python3 -c "import yaml; yaml.safe_load(open('deploy.yml'))"` raises `yaml.constructor.ConstructorError` or `yaml.scanner.ScannerError` at the same line as the Actions error
  • · `actionlint .github/workflows/deploy.yml` reports the exact line and rule that failed
  • · `cat -A .github/workflows/deploy.yml | grep -n "^\^I"` returns lines whose first non-whitespace character is a tab
  • · The commit that added the file is in the last hour, by an author who has not previously added a workflow file
  • · `gh workflow list` includes the new file with status `disabled` or `parse_error`; `gh workflow run` against it returns "workflow file is invalid"
  • · No other file in `.github/workflows/` was modified in the same commit
  • · The PR that added the file has all other checks green
Diagnosis and resolutionclick to reveal

Root cause

YAML's grammar forbids tabs in indentation. A tab character at the start of a line, or at the start of an indented key, is a hard parse error — the parser cannot guess what column the author meant, so it raises rather than recovers. The workflow file in this incident was edited in an editor whose default indentation mode inserts a tab on the first press of the Tab key, then spaces on subsequent presses (or vice versa), and the engineer's muscle memory produced a mixed-indent file. GitHub Actions parses every workflow file at push time and refuses to register a workflow that does not parse, which is the correct behaviour — a workflow that runs but behaves unexpectedly because of a parse tolerance is worse than a workflow that refuses to run at all. The structural failure is that there is no lint step that catches the problem locally before the file reaches the remote.

Remediation

Validate locally before pushing: `actionlint -color .github/workflows/` — or, if `actionlint` is not installed, `python3 -c "import yaml; yaml.safe_load(open('deploy.yml'))"` as a fallback that catches the parse error but not the schema error. Reformat the file with `yamlfmt` or by hand to use spaces only; `cat -A deploy.yml | grep -n "^\^I"` should return nothing. Verify with `gh workflow view deploy.yml` that the parser error is gone, then `gh workflow run deploy.yml` against a non-default branch to confirm the workflow actually executes. Push the corrected file; the next push event will register the workflow and the Actions tab will go green.

Verification

`gh workflow view deploy.yml` returns the workflow metadata without a parse error. The Actions tab shows the next push event with a green check on the workflow run, not a red X with "Invalid workflow file". `actionlint .github/workflows/deploy.yml` exits 0. A `pre-commit` framework or editor plugin prevents the same author from producing the same mistake on the next workflow file.

Prevention

Catch parse errors before the file leaves the developer. Add a `pre-commit` framework configuration (`.pre-commit-config.yaml` checked into the repository) that runs `actionlint` on any file under `.github/workflows/`; this is the structural difference between a hook on one laptop and a control that every developer and CI runner picks up by cloning the repository. Add a CI job that runs `actionlint` and `gh workflow validate` on every PR that touches `.github/workflows/**` and fails the PR if either reports an error. Configure the editor config: `.editorconfig` with `indent_style = space`, `indent_size = 2` for `*.yml`, and an editor extension that converts tabs to spaces on save. The push-time validation GitHub Actions already performs is a backstop, not a primary control — by the time it fires, the PR is already failing.

YAML forbids tabs in indentation, and a workflow file with mixed tabs and spaces will not parse at all. GitHub Actions refusing to register an invalid workflow is the correct behaviour — the structural control is to catch the parse error before the file leaves the developer, not after.