YAML is easy to read and surprisingly easy to break. Its structure comes from indentation, many characters have special meanings, and a value that looks like a string can quietly become a boolean or a number. The result is a deploy that fails with mapping values are not allowed here, or worse, a config that loads without any error but means something you did not intend.
This guide covers the ten mistakes behind almost every YAML problem: what the error looks like, why it happens and how to fix it.
Quickest fix: paste the file into the YAML Validator. It shows the exact line and column of the error, highlights the line, and warns about values that different parsers read differently.
Error messages below are quoted from two places: DevBrace (the free YAML Validator I built) and PyYAML, the parser behind Ansible and most Python tools. Other parsers word things differently, but the causes are the same.
1. Tabs used for indentation
server:
port: 8080
The YAML specification forbids tab characters for indentation, full stop. Editors that insert tabs, or copy-paste from a web page, are the usual culprits — and tabs are invisible, so the file looks fine.
-
DevBrace:
Tabs are not allowed as indentation(line 2) -
PyYAML:
found character '\t' that cannot start any token
Fix: replace tabs with spaces. Set your editor to "insert spaces" for .yaml and .yml files. The YAML Formatter rewrites the whole file with consistent space indentation once it parses.
2. Inconsistent indentation
server:
host: example.com
port: 8080
host and port are meant to be siblings, but port has three spaces instead of two. To a YAML parser, a different indent means a different level of nesting, so the line no longer fits anywhere.
-
DevBrace:
Nested mappings are not allowed in compact mappings(line 2)
Fix: keys at the same level must start in exactly the same column. Two spaces per level is the convention for Kubernetes, Docker Compose, GitHub Actions and Ansible. When an error points to a line that looks correct, compare its indentation with the line above it.
3. No space after the colon
name:api
port: 8080
A key and its value are separated by a colon followed by a space. Without the space, name:api is not a key at all. The parser reads it together with the next line as one long key that spans two lines, which is not allowed.
-
DevBrace:
Implicit keys need to be on a single line(line 1)
Fix: write name: api. This is also why URLs work unquoted: in url: https://example.com:8443/api, none of the colons inside the value are followed by a space.
4. A colon followed by a space inside a value
message: Error: disk full
The first : separates the key from the value. The second : makes the parser think the value is itself a key, starting a nested mapping on a single line.
-
DevBrace:
Nested mappings are not allowed in compact mappings(line 1, column 10) -
PyYAML:
mapping values are not allowed here
Fix: quote the value: message: "Error: disk full". Any value containing : or # (a space followed by a hash, which starts a comment) should be quoted.
5. List items and keys mixed at the same level
items:
- apple
banana: 2
A node is either a list (- item) or a mapping (key: value), never both. Here items starts as a list, then a key appears at the same indentation.
-
DevBrace:
All mapping items must start at the same column(line 3)
Fix: decide what the data is. Either make every entry a list item:
items:
- apple
- banana: 2
or make items a mapping with named keys. Converting the file with YAML to JSON is the fastest way to see which shape you actually wrote.
6. Unclosed quotes and brackets
title: "Hello world
next: 1
ports: [80, 443
host: example.com
A double-quoted string runs until the next unescaped ", even across lines, and a flow list runs until its ]. One missing character swallows the rest of the file, so the reported line is often far below the real mistake.
-
DevBrace:
Missing closing "quoteandFlow sequence in block collection must be sufficiently indented and end with a ]
Fix: close the quote or bracket. If the error line makes no sense, search upwards for the last ", ', [ or { that was opened on the same logical value.
7. Duplicate keys
port: 80
host: example.com
port: 8080
The specification says keys in a mapping must be unique, but parsers disagree on what to do about it. PyYAML silently keeps the last value, while JavaScript's js-yaml and Go's yaml.v3 report an error. That means the same file can load in a Python script and fail in a Node or Go service — or load with a value you did not expect.
-
DevBrace:
Map keys must be unique(line 3)
Fix: remove or rename one of the keys. Duplicates usually appear after merging two versions of a file, so check the diff that introduced them.
8. Undefined aliases — and values starting with * or &
prod:
<<: *defaults
replicas: 3
In YAML, &name defines an anchor and *name refers back to it. An alias must come after its anchor, and the anchor must exist in the same document.
-
DevBrace:
Unknown alias *defaults: no anchor &defaults is defined before it(line 2) -
PyYAML:
found undefined alias 'defaults'
The same rule catches values that merely look like aliases. pattern: *.log is read as a reference to an anchor named .log, not as a glob.
Fix: define &defaults above the alias, or quote text that starts with * or &: pattern: "*.log".
9. Values starting with a reserved character
email: @jane
template: { name }
Several characters cannot start a plain (unquoted) value: @ and ` are reserved, { and [ start a flow mapping or list, ! starts a tag, | and > start a block scalar, and % starts a directive.
-
DevBrace:
Plain value cannot start with reserved character @(line 1)
The second line is subtler. { name } is valid YAML, but it is a mapping, not the text { name }. The same thing happens with the double-brace template expressions used by Ansible and Helm: an unquoted value that starts with a brace becomes a nested mapping, and PyYAML fails with found unhashable key.
Fix: quote the value: email: "@jane", template: "{ name }". Quote every template expression that starts a value.
10. Values that silently change type
This one produces no error at all, which is what makes it dangerous.
country: NO
enabled: yes
mode: 0644
version: 1.10
zip: 02134
name:
| Value | YAML 1.2 parsers | YAML 1.1 parsers (PyYAML, Ruby) |
|---|---|---|
NO |
the string "NO"
|
the boolean false
|
yes, on, off
|
strings | booleans |
0644 |
the number 644 | the octal number 420 |
1.10 |
the number 1.1 | the number 1.1 |
02134 |
the number 2134 | the octal number 1116 |
name: (nothing) |
null |
null |
The first row is the famous Norway problem: a list of country codes where Norway turns into false. The version: 1.10 row bites everyone eventually — 1.10 and 1.1 are the same number, so the version you wrote is not the version your program reads.
The YAML Validator flags NO, yes, off and 0644 with a warning and the line number, because the file is valid but means different things to different tools.
Fix: quote anything that must stay text: country: "NO", version: "1.10", zip: "02134". If you generate YAML from JSON, the JSON to YAML converter adds these quotes for you.
A debugging checklist
- Validate first. Paste the file into the YAML Validator and go to the reported line.
- Look above the reported line. Indentation, unclosed quotes and missing colons are usually caused one or two lines earlier.
- Check for tabs. Turn on "show whitespace" in your editor.
-
Quote suspicious values. Anything with
:,#, a leading@,*,&,{,[, or a value likeyes,no,on,off, version numbers and leading zeros. - See what the parser sees. Convert to JSON with YAML to JSON: the explicit brackets and quotes make wrong nesting and wrong types obvious.
- Normalise the file. Once it is valid, run it through the YAML Formatter so indentation stays consistent for the next person who edits it.
Catching YAML errors before they ship
A validator in the browser is perfect while editing. In a repository, add a check to CI so a broken file never reaches production:
# Syntax check with Python (PyYAML)
python -c 'import sys, yaml; [list(yaml.safe_load_all(open(f))) for f in sys.argv[1:]]' config/*.yaml
# Style and syntax with yamllint
yamllint -d '{extends: default, rules: {truthy: {check-keys: false}}}' .
yamllint's truthy rule catches the Norway problem, its key-duplicates rule catches duplicate keys, and indentation catches the rest.
Most YAML errors take seconds to fix once you know which of these ten you are looking at.
Originally published on DevBrace, where you can also try the YAML Validator used in the examples.
Top comments (0)