YAML Gotchas: The Norway Problem, Tabs and Indentation Errors
Why YAML turns NO into false, 0755 into 493 and 1.10 into 1.1, why tabs break files, and how YAML 1.1 and 1.2 parsers disagree about the same document.
YAML is popular for configuration because it looks like plain text. That is also its main trap: values that look like strings are often parsed as booleans, numbers or nulls, and indentation that looks fine in your editor can be invalid. The file loads without an error and the program then behaves wrongly, which is much harder to debug than a crash.
The quickest check is to paste the file into the validator below and read the JSON it produces. JSON has no implicit typing, so every surprise becomes visible: you see false where you expected "NO", or 1.1 where you wrote 1.10.
First: which YAML are you parsing?
Most YAML surprises depend on the version of the spec your parser follows, and the two versions in common use disagree.
- YAML 1.1 (2005) recognises
yes,no,on,off,yandnas booleans, reads a leading zero as octal and reads12:30as a base-60 number. PyYAML and Go'sgopkg.in/yaml.v2follow it, and so do the many tools built on them. - YAML 1.2 (2009) dropped most of that. Only
trueandfalseare booleans and only0ostarts an octal number. js-yaml 4, which this site's validator uses, follows the 1.2 core schema, as do ruamel.yaml and Go'syaml.v3.
Here is the same document read both ways:
country: NO
debug: on
mode: 0755
start: 12:30
| Key | YAML 1.1 (PyYAML) | YAML 1.2 (js-yaml) |
|---|---|---|
country: NO | false | "NO" |
debug: on | true | "on" |
mode: 0755 | 493 (octal) | 755 |
start: 12:30 | 750 (minutes) | "12:30" |
Not one of these four values parses the same way in both. A file that passes a 1.2 validator can still be misread by a 1.1 tool in your deployment pipeline, so find out which parser actually consumes your file.
The Norway problem
The best-known example: a list of country codes, where Norway's code silently turns into a boolean.
countries:
- GB
- FR
- NO
A YAML 1.1 parser returns ["GB", "FR", false]. The same trap catches y and n in survey answers, on and off in feature flags and, famously, the on: key at the top of every GitHub Actions workflow: PyYAML loads it as the boolean key True, not the string "on".
The fix is the same in every case: quote any value that must stay a string.
countries:
- "GB"
- "FR"
- "NO"
Quoting is cheap and works under both versions. Many teams quote every string in a shared config file so nobody has to remember the list of special words.
Numbers that lose information
These catch people even under YAML 1.2, because they are numbers by any reading.
Version numbers
python: 3.10
This is the float 3.1, so a CI matrix asking for Python 3.10 installs 3.1 or fails to find it. Anything with meaningful trailing zeros, such as versions, must be quoted: python: "3.10". A value with two dots, such as 1.2.3, is not a valid number and stays a string, which is why the bug only appears for some versions.
Leading zeros
zip: 01234
phone: 0812345678
Under YAML 1.2 these become 1234 and 812345678, and the leading zero is gone. Under YAML 1.1, 01234 is octal and becomes 668. Postcodes, phone numbers, account numbers and anything else that is an identifier rather than a quantity should be quoted.
Large integers
id: 12345678901234567890
The parser reads this correctly, but a JavaScript consumer stores it as a double and you get 12345678901234567000. This is the same limit that affects JSON: IDs larger than 2^53 are safer as strings.
Things that become null
All three of these are null, not an empty string:
a: ~
b: null
c:
An empty value is the one that causes bugs, usually when a template variable expands to nothing. If you mean an empty string, write c: "".
Tabs and indentation
YAML forbids tab characters in indentation. js-yaml reports it plainly:
tab characters must not be used in indentation (2:1)
The tricky part is that a tab and two spaces can look identical in an editor. If the reported line looks fine, turn on visible whitespace or search for a tab character. Setting your editor to insert spaces for .yml and .yaml files prevents the problem altogether.
Mismatched indentation is the other common error:
server:
host: example.com
port: 8080
port is indented by one space, which belongs to neither server nor the root, so the parser reports bad indentation of a mapping entry. Siblings must line up exactly. YAML does not care whether you use two spaces or four, only that you are consistent inside a block.
Colons, hashes and other characters
A plain (unquoted) string cannot contain a colon followed by a space, because that is what starts a key:
message: Note: restart required
This fails to parse. Quote the whole value: message: "Note: restart required".
A # preceded by a space starts a comment, which can silently cut off a value:
password: abc #123
The value is "abc", and no error is reported. A # with no space before it, as in http://example.com/#top, is kept. Passwords and tokens in YAML should always be quoted.
Values starting with *, &, !, @, a backtick, %, [ or { also need quotes, because those characters have structural meaning at the start of a scalar.
Multi-line strings
Block scalars are useful, but the indicator after the pipe or angle bracket changes the result:
literal: |
line one
line two
folded: >
line one
line two
stripped: |-
line one
literal keeps the line breaks: "line one\nline two\n". folded joins the lines with spaces: "line one line two\n". Both keep a trailing newline, and |- removes it. That trailing newline is a frequent cause of "the value has an extra line break" bugs, for example in a certificate or key that is compared byte for byte.
Duplicate keys
replicas: 2
replicas: 5
js-yaml rejects this with duplicated mapping key. Some other parsers keep the last value without warning. When a long file is edited by several people, a duplicate key can quietly cancel someone's change, so a parser that refuses duplicates is the safer one.
A checklist before you commit a YAML file
- Quote country codes,
yes/no/on/off, versions, postcodes, phone numbers and secrets. - Use spaces, never tabs, and keep siblings aligned exactly.
- Write
""when you mean an empty string. - Find out whether the program reading the file uses YAML 1.1 or 1.2.
- Convert the file to JSON and read the result. Every type is explicit there, so anything YAML guessed wrongly stands out.
Once a config is converted, the JSON formatter is a convenient place to read or tidy the output.
JSON FormatterPaste JSON to indent it readably and check that it parses, with the line and column of any error and a collapsible tree for finding your way around an unfamiliar response.