Skip to content
datahacks.dev
YAML

Fixing YAML Indentation Errors With a Linter and Editor

A messy file with nine findings, reported by yamllint line by line, then repaired with yq and a project config. The workflow that finds and fixes indentation.

7 min read
Summarize in AI
Flat illustration of a tree of nested objects beside the document it came from, with the YAML logo.

Most YAML indentation faults are easy to repair once something lists them. The expensive part is finding them by eye in a file with hundreds of lines. A linter lists every one with a line number and a rule name, and a formatter rewrites the layout so the rest disappears. This post runs both on a deliberately messy sample, shows the output at each step and ends with the point where a human has to decide.

The messy file

YAML
name: demo
enabled: yes
retries:   3
items:
    - one
    -   two
paths: [ "/a", "/b" ]
very_long_key: this line is deliberately long so that it goes past the default eighty character limit of yamllint ok
list:
- a
- b
 # odd comment

It parses without error, which is part of the point: a parser accepts it, and a reviewer would reject it. Problems include extra spaces after a colon, a list indented four spaces, an extra space after a dash, spaces inside brackets, a long line, a list not indented under its key and a comment indented by one space.

Step 1: lint

yamllint with the default ruleset reports each finding with a position, a level and the rule that fired:

Text
messy.yaml
  1:1       warning  missing document start "---"  (document-start)
  2:10      warning  truthy value should be one of [false, true]  (truthy)
  3:11      error    too many spaces after colon  (colons)
  6:8       error    too many spaces after hyphen  (hyphens)
  7:9       error    too many spaces inside brackets  (brackets)
  7:20      error    too many spaces inside brackets  (brackets)
  8:81      error    line too long (116 > 80 characters)  (line-length)
  10:1      error    wrong indentation: expected 4 but found 0  (indentation)
  12:2      warning  comment not indented like content  (comments-indentation)

Nine findings. Notice what the indentation rule says: line 10 (- a) has "expected 4 but found 0". The default rule infers the indentation from the first nested block it sees, the four-space list at line 5, and treats the rest as deviations. It is a reminder that a linter reports inconsistency, not error: nothing was wrong with line 10 until line 5 set a standard. The finding at 5:5 does not appear in the default run because four spaces was taken as the norm.

A project config states the norm explicitly:

.yamllintYAML
extends: default
rules:
  line-length: {max: 100, level: warning}
  truthy: {allowed-values: ["true", "false"], check-keys: false}
  document-start: disable
  indentation: {spaces: 2, indent-sequences: consistent}
Text
messy.yaml
  2:10      warning  truthy value should be one of [false, true]  (truthy)
  3:11      error    too many spaces after colon  (colons)
  5:5       error    wrong indentation: expected 2 but found 4  (indentation)
  6:8       error    too many spaces after hyphen  (hyphens)
  7:9       error    too many spaces inside brackets  (brackets)
  7:20      error    too many spaces inside brackets  (brackets)
  8:101     warning  line too long (116 > 100 characters)  (line-length)
  10:1      error    wrong indentation: expected 2 but found 0  (indentation)
  12:2      warning  comment not indented like content  (comments-indentation)

exit=1

Now the norm is two spaces, and the four-space list at line 5 is the offender. Config is where a team's convention lives. The exit status is 1 whenever there are errors (warnings alone exit 0), which is what makes the linter usable in CI: an error fails the job. A parsable output format (-f parsable) gives one finding per line, messy.yaml:3:11: [error] too many spaces after colon (colons), ready for editors and CI annotations.

Step 2: reformat

Many layout findings are mechanical. yq -P ("pretty print") parses the file and writes it back in a normal layout:

Text
name: demo
enabled: yes
retries: 3
items:
  - one
  - two
paths:
  - /a
  - /b
very_long_key: this line is deliberately long so that it goes past the default eighty character limit of yamllint ok
list:
  - a
  - b
  # odd comment

The extra spaces after the colon and the dash are gone. The four-space list is two-space. The flow list [ "/a", "/b" ] became a block list, and the quotes around the plain strings were dropped because they were not needed. The list under list: is now indented under its key, and the odd comment is aligned with the content. Linting the reformatted file against the project config:

Text
messy_fixed.yaml
  2:10      warning  truthy value should be one of [false, true]  (truthy)
  10:101    warning  line too long (116 > 100 characters)  (line-length)

exit=0

Two warnings are left, and they are exactly the ones that need a person. enabled: yes is a boolean in some parsers and a string in others, and only the author knows what was meant: write true, or quote it. The long line is a long value: fold it into a block scalar or shorten it. A formatter cannot decide either.

Be careful what yq -P removes. It dropped the quotes around "/a" and "/b", correct here because they are plain strings. It would also unquote a value like "NO" if its parser considers the quotes unnecessary under YAML 1.2 rules, producing a file that a 1.1 parser reads differently. Check the diff after a reformat for any value that was quoted on purpose (YAML 1.1 versus 1.2).

Step 3: what a formatter cannot fix

A line indented between two levels has no repair that a tool can choose for you. In

YAML
a:
    b: 1
  c: 2

the author may have meant c to be a sibling of a (column 1) or of b (column 5). The parser refuses to guess and a formatter will fail or choose one, and either can change the meaning (YAML error: did not find expected key). The rule is to fix syntax errors by hand first, so the file parses, then run the formatter, then read the diff for structure changes. Never run a formatter and commit without reading what moved.

The workflow, as a script

  1. yamllint file.yaml and read the errors. Fix every syntax error by hand.

  2. yq -P -i file.yaml (or your formatter) to normalize layout. Review the diff for changed quotes and moved comments.

  3. yamllint file.yaml again. The remaining findings are the ones that need judgment.

  4. Commit the reformat separately from content changes, so later diffs are clean.

  5. Put yamllint in CI and in a pre-commit hook, with the project config, so the file stays that way.

Editors close the loop: a YAML extension that runs the linter as you type underlines the offending line before you save.

Putting it into the workflow

The three steps are quick when they are wired into the tools you already use. In an editor, a YAML language-server extension shows the linter's findings as underlines as you type, and many offer a format-on-save action backed by the same formatter you run in CI, so what you see matches what the pipeline checks. In version control, a pre-commit hook runs yamllint on the staged files and rejects a commit that has errors, which turns the fix into a ten-second loop on the developer's machine instead of a failed build twenty minutes later.

In CI, run the linter with the project config on the whole repository, and let it fail the build on errors. Treat warnings as a ratchet: start by allowing them, fix the ones that matter, and tighten the rule when the count reaches zero. A repository with hundreds of existing findings cannot adopt a strict linter on day one, so adopt it for changed files first. Most CI systems can pass the list of files changed in a pull request to the linter, and the debt shrinks as files are touched.

Agree on the rules in the pull request that adds the config, not afterwards. The default ruleset is opinionated about things like the document start marker and line length, and the arguments are cheaper before the first failure than after. Write the decisions into the config as comments, and keep the file short. A config that disables half the rules is a sign the team has not agreed what the rest are for.

The cluster hub is fixing YAML errors that make no sense. For the linter itself, see linting YAML with yamllint, and for the indentation rules YAML indentation rules and why tabs break everything. The rules are documented in the yamllint rule reference, and yq in its manual. Reformat in a browser with the YAML formatter.

When a linter cannot repair a file for you

The structure is ambiguous. A line between levels needs a decision from the author.

The file is a template. Linters parse YAML, and files with placeholders are not YAML until rendered. Lint the rendered output.

The formatter changes meaning. Dropping quotes, reordering keys or rewriting block scalars can alter what a different parser reads. Review the diff, and prefer a round-trip tool.

Style is contested. If two tools disagree about the layout, they will fight in every commit. Choose one formatter and one linter config for the repository.

Also inTooling
Share

Keep reading

Get new articles by email

We send one email when a new article is published. No spam.

Unsubscribe in one click. No spam, ever.