The Same YAML, Eight Different Answers
We parsed 22 tricky YAML values with eight parsers. NO, on, y, 0644 and duplicate keys came back differently, and Kubernetes turned a key y into true.
Ben Ennis
Published October 20, 2026
A ConfigMap ships with COUNTRY: NO. kubectl apply fails with cannot unmarshal bool into Go struct field ConfigMap.data of type string. Someone quotes the value, the deploy goes through, and the team moves on. A month later a Helm chart sets a coordinate with an unquoted key y, and nothing fails at all. The data lands in the cluster under the key "true".
Both are the same bug. The YAML was valid, and the parser did exactly what its rules say. The rules depend on which parser you use. To see how far apart they are in practice, we fed the same 22 snippets to eight YAML libraries that developer tooling depends on and recorded what each one returned.
Why parsers disagree
YAML 1.1, from 2005, resolves a long list of plain words as booleans. The YAML 1.1 boolean type accepts y|Y|yes|Yes|YES|n|N|no|No|NO|true|True|TRUE|false|False|FALSE|on|On|ON|off|Off|OFF. Its integer rules treat a leading zero as octal, allow _ separators, and read 1:30 as base 60. It also defines the << merge key.
YAML 1.2 replaced that with a much smaller core schema. Only true and false, in three capitalizations, are booleans. Octal needs a 0o prefix. If none of the core patterns match, “the scalar is resolved to tag:yaml.org,2002:str.” Merge keys aren’t part of 1.2 at all.
Libraries picked a side at different times, and many never moved. The result is that “YAML” names a family of slightly different languages, and the one you get depends on the parser.
The test
We ran each case through each library’s default safe loader with default options on October 20, 2026:
- Python: PyYAML 6.0.3 (
safe_load) and ruamel.yaml 0.19.1 (typ="safe") - JavaScript: js-yaml 3.15.2, js-yaml 5.4.2, and yaml 2.9.1
- Go: gopkg.in/yaml.v2 2.4.0, go.yaml.in/yaml/v3 3.0.5, and sigs.k8s.io/yaml 1.6.0
The last one is what Kubernetes uses. The Kubernetes v1.37.1 go.mod pins sigs.k8s.io/yaml 1.6.0 and go.yaml.in/yaml/v2 2.4.4. The library’s README says it “first converts YAML to JSON using go-yaml and then uses json.Marshal and json.Unmarshal.” Every case and every raw result is published as JSON.
Results
| Input | PyYAML | ruamel | js-yaml 3 | js-yaml 5 | yaml 2 | go-yaml v2 | go-yaml v3 | Kubernetes |
|---|---|---|---|---|---|---|---|---|
country: NO |
false | “NO” | “NO” | “NO” | “NO” | false | “NO” | false |
enabled: on |
true | “on” | “on” | “on” | “on” | true | “on” | true |
answer: y |
“y” | “y” | “y” | “y” | “y” | true | “y” | true |
on: as a key |
true | “on” | “on” | “on” | “on” | “true” | “on” | “true” |
mode: 0644 |
420 | 644 | 420 | 644 | 644 | 420 | 420 | 420 |
mode: 0o644 |
“0o644” | 420 | “0o644” | 420 | 420 | 420 | 420 | 420 |
zip: 08080 |
“08080” | 8080 | “08080” | 8080 | 8080 | 8080 | 8080 | 8080 |
duration: 1:30 |
90 | “1:30” | 90 | “1:30” | “1:30” | “1:30” | “1:30” | “1:30” |
limit: 1e3 |
“1e3” | 1000 | 1000 | 1000 | 1000 | 1000 | 1000 | 1000 |
count: 1_000 |
1000 | 1000 | 1000 | “1_000” | “1_000” | 1000 | 1000 | 1000 |
date: 2026-10-20 |
date | date | Date | string | string | string | time | string |
Duplicate key a |
last wins | error | error | error | error | last wins | error | last wins |
<< merge key |
merged | merged | merged | literal key | literal key | merged | merged | merged |
A few cases agreed everywhere. version: 1.10 became the number 1.1 in all eight, which loses the trailing zero. 0x1F was 31 everywhere, ~ and Null were null everywhere, and .inf parsed as infinity everywhere. Kubernetes then rejected .inf when converting to JSON (json: unsupported value: +Inf). The three JavaScript libraries also turned 12345678901234567890 into 12345678901234567000, because JavaScript numbers are 64-bit floats.
What the table says
No two columns match. Even libraries that claim the same YAML version differ. go-yaml v3 reads NO as a string, like YAML 1.2, but still treats 0644 as octal, accepts 1_000, and applies merge keys. PyYAML and js-yaml 3 both read 0644 as octal, but only PyYAML rejects 1e3 as a number, because YAML 1.1 floats need a decimal point.
Kubernetes behaves like YAML 1.1. Its column matches go-yaml v2, booleans and all, because sigs.k8s.io/yaml is built on the v2 fork. y, n, yes, no, on, and off are all booleans in Kubernetes manifests. That includes keys. JSON keys must be strings, so a boolean key gets stringified to "true" or "false".
Some failures are loud and some are silent. We decoded test ConfigMaps with k8s.io/apimachinery 0.37.1 to see both paths:
COUNTRY: NOconverts to the JSON"COUNTRY": false. Strict JSON decoding into a ConfigMap then fails withcannot unmarshal bool into Go struct field ConfigMap.data of type string. That is the familiar error, and it at least stops the deploy.PORT: 8080fails the same way, withcannot unmarshal number.y: "2"converts to"true": "2". The value is a valid string, so nothing fails. The ConfigMap is stored with the wrong key.- Decoding the same YAML straight into a typed Go struct, which many controllers and operators do, didn’t fail at all.
COUNTRYcame back as the string"false"andPORTas"8080".
The loud cases get fixed in the first deploy. The silent ones, a renamed key or a value that is now the text “false,” get found in production.
Duplicate keys are a coin flip. PyYAML, go-yaml v2, and Kubernetes keep the last value without a warning. The other five refuse to parse. A file that one tool accepts can fail in another, and a merge conflict resolved badly can keep the wrong value without anyone noticing.
Merge keys are not portable. js-yaml 5 and yaml 2 kept << as an ordinary key in our default-options test. The yaml docs say merge support “depends on YAML version” and is off by default for 1.2, because “merge keys are a YAML 1.1 feature that is not a part of the 1.2 spec.” Anchors and << that work in a Python or Go pipeline can silently produce a different structure in a Node.js tool.
The GitHub Actions on: case
The on: row explains a puzzle many people have hit. GitHub Actions workflows start with on:. GitHub’s own parser reads that as the string “on,” which is how the workflow syntax reference uses it. Load the same file with PyYAML to lint or generate it, and the key comes back as the boolean True. Write it back out, and the key becomes true:, which breaks the workflow. Tools that rewrite workflow files need a YAML 1.2 parser such as ruamel.yaml, or they need to quote the key as "on":.
How to avoid it
- Quote strings that could look like anything else. Country and language codes,
yes/noflags meant as text, version numbers, zip codes, file modes, times, and single-letter keys likeyandn."NO","1.10","0644", and"y":are unambiguous in every parser we tested. - Write file modes as decimal in Kubernetes. For
defaultModeon Secret and ConfigMap volumes, write420, not0644. Both work today in Kubernetes, but0644is 644 in a YAML 1.2 tool that edits the file before it reaches the cluster. - Don’t rely on
<<in files that more than one tool reads. Expand anchors in generated output, or confirm every consumer supports merge keys. - Fail on duplicate keys in CI. Run a strict parser such as ruamel.yaml or yamllint’s
key-duplicatesrule over your repository, so a duplicate fails a check instead of choosing a value. - Check what Kubernetes actually stored. After an apply, compare
kubectl get configmap app -o jsonwith the source file. JSON shows the key and value exactly as stored, so a"true"key or a"false"value is easy to see. - Consider KYAML for Kubernetes output. The KYAML guide covers the Kubernetes output format that double-quotes all value strings to avoid exactly this class of bug.
To see how a snippet converts before it ships, paste it into the YAML to JSON converter. JSON makes every type explicit. For Kubernetes manifests, the Kubernetes YAML linter catches common structural mistakes as well.
Recommended reading
Frequently asked questions
Why does YAML turn NO into false?+
Does Kubernetes use YAML 1.1 or 1.2?+
Why did my ConfigMap key y become true?+
Is 0644 octal in YAML?+
Are duplicate keys allowed in YAML?+
How do I make YAML parse the same everywhere?+
Tags: #yaml, #json, #kubernetes, #parsers, #norway-problem, #incident, #original-data