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.

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
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 commentIt 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:
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:
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}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=1Now 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:
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 commentThe 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:
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=0Two 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
a:
b: 1
c: 2the 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
yamllint file.yamland read the errors. Fix every syntax error by hand.yq -P -i file.yaml(or your formatter) to normalize layout. Review the diff for changed quotes and moved comments.yamllint file.yamlagain. The remaining findings are the ones that need judgment.Commit the reformat separately from content changes, so later diffs are clean.
Put
yamllintin 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.
Related reading
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.


