YAML is the configuration language of modern CI/CD — and it's uniquely terrible at telling you what you did wrong. A misplaced space, a tab character, or a missing colon can break your entire pipeline with an error message that points to the wrong line.
This isn't a YAML tutorial. It's a field guide to the specific YAML mistakes that waste the most engineering time in CI/CD pipelines, with real broken examples and their fixes shown side by side.
Why YAML Is Uniquely Bad for This
Most programming languages have compilers or interpreters that give you precise error messages with line numbers. YAML has none of this:
- Whitespace-sensitive — indentation determines structure, but there's no visual difference between 2 spaces and 3 spaces
- No compiler feedback loop — you push, wait for CI to start, and then find out your YAML is broken. The feedback loop is minutes, not milliseconds
- Error messages are misleading — YAML parsers often report errors at the point where the consequence of the mistake is detected, not where the mistake actually is
- Platform-specific extensions — GitHub Actions YAML isn't the same as GitLab CI YAML. Valid syntax for one may be invalid for the other
Mistake 1: Indentation Misalignment
The #1 YAML mistake in CI/CD. Off-by-one indentation changes the meaning of your entire pipeline:
Broken:
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install deps
run: npm ci
- name: Test # ← 7 spaces instead of 8
run: npm test
Fixed:
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install deps
run: npm ci
- name: Test # ← 6 spaces, aligned with other list items
run: npm test
The error message won't say "indentation wrong on line 8." It'll say something like mapping values are not allowed in this context and point to a completely different line.
Mistake 2: Tabs vs. Spaces
YAML does not allow tab characters for indentation. Period. But many editors insert tabs by default, and tabs are invisible in most UIs.
The trap: You copy a YAML snippet from a blog post that used tabs. It looks identical to properly-indented YAML. Your pipeline fails with a cryptic parser error.
How to detect:
# Find tabs in your CI config
grep -P '\t' .github/workflows/*.yml
cat -A .github/workflows/ci.yml | grep '\^I'
How to prevent:
- Add
"editor.insertSpaces": trueto your VS Code settings - Add an
.editorconfigfile withindent_style = space - Use a pre-commit hook that rejects tabs in YAML files
Mistake 3: Missing Required Keys
Both GitHub Actions and GitLab CI have required keys that produce confusing errors when absent:
GitHub Actions — missing run or uses:
# Broken: step has a name but no action
- name: Build the project
# Missing: run: npm run build
Error: Every step must define a 'uses' or 'run' key
GitLab CI — missing script key:
# Broken
build:
stage: build
# Missing: script: key
image: node:20
Error: jobs:build config should implement a script: or a trigger: keyword
This one catches people who are used to GitHub Actions syntax trying to write GitLab CI config, or vice versa.
Mistake 4: Multiline String Gotchas
YAML has multiple ways to write multiline strings, and each behaves differently. This is where even experienced engineers trip up:
Pipe (|) — preserves newlines:
- name: Multi-command step
run: |
echo "Line 1"
echo "Line 2"
npm test
Greater-than (>) — folds into a single line:
# This becomes ONE line: "echo hello echo world"
- name: Broken multi-command
run: >
echo hello
echo world
The > block scalar folds newlines into spaces. If you want multiple shell commands, you need |, not >. Using > will concatenate your commands into one nonsensical string.
Common trap — indentation inside multiline blocks:
# Broken: first line of block must be indented relative to the key
- run: |
echo "this will fail" # ← not indented under run:
# Fixed
- run: |
echo "this works" # ← indented under run:
Mistake 5: Anchors and Aliases Gone Wrong
YAML anchors (&) and aliases (*) let you reuse configuration. They're powerful but fragile:
Broken — alias before anchor:
# Can't use *defaults before defining &defaults
deploy:
<<: *defaults
script: ./deploy.sh
.base: &defaults
image: node:20
before_script:
- npm ci
Fixed — anchor must come first:
.base: &defaults
image: node:20
before_script:
- npm ci
deploy:
<<: *defaults
script: ./deploy.sh
Note: GitHub Actions doesn't support YAML anchors at all. This is GitLab CI-specific.
Prevention: Validate Before You Push
The single best habit for avoiding YAML errors is validating locally before pushing:
Tools:
- GitLab CI Lint — built into GitLab at
/-/ci/lint. Paste your.gitlab-ci.ymland it validates both syntax and CI semantics yamllint— catches formatting issues:pip install yamllint && yamllint .github/workflows/- VS Code YAML extension — real-time schema validation. Install the Red Hat YAML extension and add schema mappings for your CI platform
actionlint— purpose-built for GitHub Actions. Checks syntax, expression types, and action versions:brew install actionlint && actionlint
Pre-commit hook example:
# .pre-commit-config.yaml
repos:
- repo: https://github.com/adrienverge/yamllint
rev: v1.35.1
hooks:
- id: yamllint
args: [-d, relaxed]
Syntax errors are the easy case — the pipeline won't even start. The harder case is when the YAML is valid but the logic is wrong — that's where log-level analysis like Daxtack's actually earns its keep, since there's no linter for "this job ran but did the wrong thing."