The story is a classic. You have a list of ISO country codes in a YAML config. Norway's code is NO. Your parser reads it as the boolean false, Norway vanishes from the list, and something downstream breaks in a way that takes an afternoon to trace.
The story is also, increasingly, out of date — and the advice people repeat alongside it is not quite right. Whether no becomes false depends on which version of the YAML spec your parser implements, and the two common answers disagree completely.
The same file, two parsers
Here is the test document:
country: no
enabled: yes
off_switch: off
version: 1.0
zip: 01234
when: 2024-01-01
time_like: 12:30:00Loaded with PyYAML 6.0.3 via yaml.safe_load, which implements the YAML 1.1 resolver:
country -> False (bool)
enabled -> True (bool)
off_switch -> False (bool)
version -> 1.0 (float)
zip -> 668 (int)
when -> datetime.date(2024, 1, 1) (date)
time_like -> 45000 (int)Loaded with js-yaml 4, which defaults to the YAML 1.2 core schema:
country -> "no" (string)
enabled -> "yes" (string)
off_switch -> "off" (string)
version -> 1 (number)
zip -> 1234 (number)
when -> "2024-01-01T00:00:00.000Z" (string)
time_like -> "12:30:00" (string)Same bytes in, four materially different values out. These are real outputs, not reconstructions.
Two surprises bigger than Norway
Everyone quotes the boolean case. The other two rows in the PyYAML output deserve more attention than they get.
`zip: 01234` becomes 668
Not 1234. 668. YAML 1.1 treats a leading zero as an octal prefix, and 01234 in octal is 668 in decimal. A postal code does not merely lose its leading zero — it becomes an entirely unrelated number, with nothing to signal that it happened.
`time_like: 12:30:00` becomes 45000
YAML 1.1 has sexagesimal (base-60) integers, so 12:30:00 is read as 12×3600 + 30×60 = 45,000 seconds. A duration, a timestamp column, a cron-ish field — all silently integers.
YAML 1.2 removed both of these. If you have only ever used a 1.2 parser, you may never have met them; if your config passes through a 1.1 parser anywhere in its life, they are waiting.
Which parser do you have?
Roughly, and worth verifying rather than assuming:
The quickest way to find out is to run the seven-line document above through whatever actually loads your config, and look at the types. It takes a minute and settles the question for your stack specifically, which is worth more than any table.
The fix is two characters
Quote anything whose type you care about. That is the whole answer, it works under every parser and every spec version, and it costs nothing:
country: "no"
enabled: "yes"
version: "1.0"
zip: "01234"
when: "2024-01-01"
time_like: "12:30:00"A useful habit: quote every value that is an identifier rather than a quantity. Country codes, postal codes, version strings, phone numbers, SKUs, account numbers, git refs. If you would never do arithmetic on it, it is a string, and saying so removes an entire category of bug.
Converting to JSON makes it visible
JSON has no implicit typing: a value is quoted or it is not, and nothing is inferred from how it is spelled. That makes a round trip a good way to see what your parser actually decided.
Paste a config into the YAML to JSON converter and read the output. Anything that came out as true, false or a bare number when you meant a string is a bug you just caught for free. The converter follows the YAML 1.2 core schema, so it will also show you what a modern parser sees — which may differ from what your production stack sees, and that difference is itself worth knowing.
Going the other way, the JSON to YAML converter quotes ambiguous scalars automatically. A string "no" comes out as 'no' and a string "1.0" as '1.0', so the result survives a round trip through a 1.1 parser unchanged.