Measured 17 September 2026

The Norway problem, and other ways YAML changes your config

I ran 11 ordinary config snippets through two real parsers — PyYAML 6.0.3, which follows YAML 1.1, and this site’s own parser, which follows YAML 1.2. They produced different values for 8 of them.3 of those disagreements came with no warning from either side. The worst case is not a value at all — it is a key that renames itself.

The one that breaks workflows

Start with the snippet that a great many people have written without knowing it is ambiguous. This is the trigger block of every GitHub Actions workflow:

on: push
Same two characters, two parsers
ParserResult
YAML 1.1 (PyYAML 6.0.3){"true": "push"}
YAML 1.2 (this site){"on": "push"}

The worst one, because it is the *key* that changes rather than the value. A 1.1 parser resolves the unquoted key `on` to boolean true and then stringifies it for use as a mapping key, so the block you wrote is no longer reachable under the name you gave it. Everything downstream still parses. It simply cannot find on, because there is no longer a key by that name.

All 11 snippets

Each of these is something a person would plainly write meaning one thing. The last column is what this site’s parser said about it, which is the difference between a surprise and a warning.

PyYAML 6.0.3 against src/lib/text-tools/yaml.ts, 17 September 2026.
You wroteYAML 1.1 readsYAML 1.2 readsFlagged?
on: push{"true": "push"}{"on": "push"}ambiguous-boolean
country: NO{"country": false}{"country": "NO"}ambiguous-boolean
debug: yes ⏎ cache: off{"debug": true, "cache": false}{"debug": "yes", "cache": "off"}ambiguous-boolean, ambiguous-boolean
employee: 012345{"employee": 5349}{"employee": "012345"}
mode: 0777{"mode": 511}{"mode": "0777"}
standup: 9:30{"standup": 570}{"standup": "9:30"}
version: 1.20{"version": 1.2}{"version": 1.2}
initial: N{"initial": "N"}{"initial": "N"}ambiguous-boolean
port: 8080 ⏎ host: localhost ⏎ port: 9090{"port": 9090, "host": "localhost"}{"port": 9090, "host": "localhost"}duplicate-key
defaults: &base ⏎ retries: 3 ⏎ staging: ⏎ <<: *base{"defaults": {"retries": 3}, "staging": {"retries": 3}}{"defaults": {"retries": 3}, "staging": {"<<": "*base"}}anchor-or-alias, anchor-or-alias, merge-key
server: ⏎ port: 8080ScannerError{"server": {"port": 8080}}tab-indent

The silent ones are the dangerous ones

3 snippets produced different values in the two parsers with nothing reported by either. No error, no warning, and a result that looks entirely reasonable on inspection:

That is what makes these worse than the boolean cases. employee: 012345becoming employee 5349 is not a crash and not a type error; it is a valid id for a different person, and it will pass every check that does not already know the right answer.

Where my own parser is the wrong one

One case goes the other way, and it belongs here rather than in a footnote. The converter on this site does not implement anchors, aliases or merge keys — a documented subset, not a bug, because implementing YAML 1.2 completely is a library’s job rather than a page’s. Given shared configuration:

defaults: &base
  retries: 3
staging:
  <<: *base

PyYAML resolves the merge and returns {"defaults": {"retries": 3}, "staging": {"retries": 3}}. This site returns {"defaults": {"retries": 3}, "staging": {"<<": "*base"}} — a literal << key, which is not the configuration anyone meant. It reports all three issues, and that reporting is the only thing standing between you and a silently wrong file. The lesson for using it: the issues list is not decoration. If it names an anchor or a merge key, the output is not usable and a full YAML library is the right tool.

The tab case diverges in the same direction and is less serious: YAML forbids tabs for indentation, PyYAML refuses the whole document, and this site accepts it and tells you. Lenient is friendlier in a converter and would be wrong in a deployment pipeline.

"YAML 1.1" is not one behaviour either

The YAML 1.1 specification lists bare y and n as booleans, but PyYAML’s resolver omits them, so it keeps the string. This site’s parser warns anyway. That disagreement is the real lesson: "YAML 1.1" is not one behaviour, and a parser can differ from the specification it claims. It is worth keeping in mind whenever someone says a document is valid YAML: the specification version tells you less than the specific parser does, and the parser reading your file in production may not be the one you tested with.

What to actually do

Limits of this test

Repeat it yourself

The script runs both parsers over all 11 snippets and prints the table as JSON:

pip install pyyaml
npx tsx scripts/measure-yaml-ambiguity.mjs

For your own file, YAML to JSON shows the parsed result and lists every ambiguity it found, with line numbers. It runs in your browser, so a config file with credentials in it does not leave your machine to be checked — and the PDF tool study covers how to verify that kind of claim for yourself.