YAML 10 ๐ YAML 1.2 vs YAML 1.1 Spec Differences
The version number in a YAML file is not decorative. YAML 1.1 and YAML 1.2 disagree on what several common strings mean, and the disagreement is silent: a file that parses one way under a 1.1 parser produces different data under a 1.2 parser. The most consequential difference is the boolean set. YAML 1.1 treats on, off, yes, and no as booleans. YAML 1.2 treats them as strings, and only true and false are booleans . A GitHub Actions workflow with an on: trigger key becomes an object keyed by boolean true under Python’s yaml.safe_load, because PyYAML implements the 1.1 rules .
This chapter covers the specification-level differences that change parsed data, the parser landscape that determines which rules apply, and the migration patterns for files that need to work across both versions. It explains why the version directive is often ignored, how to detect which version a parser implements, and the changes beyond booleans that affect numbers and timestamps. The sections that follow walk through the boolean divergence, the broader schema changes, and the practical detection and migration strategies.
Key point: YAML 1.2 restricted the boolean literals to true/false and made JSON a strict subset; YAML 1.1 parsers still treat yes, no, on, and off as booleans, and the version directive does not force a parser to change its behavior.
Why the version difference matters
The on: key problem. GitHub Actions uses on: as the workflow trigger key. Under YAML 1.1, on is a boolean alias for true, so the parsed object has a boolean key instead of the string "on". A Python validation script using yaml.safe_load reports the workflow as malformed when it is correct . GitHub Actions itself uses a YAML 1.2 parser, so the workflow runs; the validator is the thing that is wrong.
The version directive problem. A file can begin with %YAML 1.2, but the directive is not a command. The specification treats directives as presentation details that processors may honor or ignore. PyYAML’s safe_load does not switch to 1.2 rules when it sees %YAML 1.2; it continues to apply its 1.1-derived resolver . The directive documents intent; it does not enforce behavior. The parser’s implementation is what determines the result.
The JSON subset problem. YAML 1.2’s primary design goal was to make JSON an official subset, so every JSON document is also valid YAML . YAML 1.1 did not have this property. A JSON file with a key "on" is valid YAML 1.2 and valid JSON; under a 1.1 parser, the quoted key is forced to string by the quotes, so it survives, but an unquoted on does not. The subset guarantee is a 1.2 feature, and it depends on the 1.2 boolean rules.
a. Boolean resolution: the core divergence
YAML 1.1 defines a bool type with a wider set of accepted literals. The type repository lists y, Y, yes, Yes, YES, n, N, no, No, NO, true, True, TRUE, false, False, FALSE, on, On, ON, off, Off, and OFF as boolean values . YAML 1.2’s core schema restricts booleans to true, false, and their capitalized variants .
# Under a YAML 1.1 parser (PyYAML safe_load)
enabled: yes # boolean true
disabled: no # boolean false
mode: on # boolean true
country: NO # boolean false (Norway problem)
# Under a YAML 1.2 parser
enabled: yes # string "yes"
disabled: no # string "no"
mode: on # string "on"
country: NO # string "NO"
The practical rule is to quote any value that could be a boolean under either version. A configuration that means the string "on" writes "on"; a configuration that means the boolean true writes true and nothing else.
b. Beyond booleans: numbers, timestamps, and the schema
The boolean change is the most visible, but not the only one. YAML 1.1 included base-60 (sexagesimal) numbers, so 60:00 resolved to the integer 3600 . YAML 1.2 removed base-60 floats, and the Go yaml.v3 package documents that it purposefully does not support them because they are “a poor design” . A file with time: 60:00 is an integer under a 1.1 parser and a string under a 1.2 parser.
Octal notation also changed. YAML 1.1 uses 0777 for octal, while YAML 1.2 uses 0o777. The Go yaml.v3 package preserves the 1.1 form for backward compatibility but also accepts the 1.2 form . A parser that implements only 1.2 may reject the leading-zero form, or treat 0777 as the decimal 777.
Timestamps are a schema question rather than a version question. YAML 1.1’s extended schema includes !!timestamp; YAML 1.2’s core schema does not, so an untagged 2026-05-05 is a string under a strict 1.2 core parser. A parser that implements the extended schema (PyYAML, for example) may still resolve it to a date. The version number does not settle the timestamp behavior; the schema does.
c. Parser detection and migration
The parser determines the behavior, not the directive. The detection strategy is to check the parser’s documentation and test it with a known input.
| Parser | YAML Version | yes |
|---|---|---|
PyYAML safe_load | 1.1-derived | boolean |
Go goccy/go-yaml | 1.2 | string |
Go yaml.v3 | 1.2 with 1.1 compat | boolean only when target is bool |
JavaScript yaml v2+ | 1.2 | string |
| Ruby Psych (2.6+) | 1.2 | string |
A quick test is to parse key: yes and check the type of the value. If it is a boolean, the parser is applying 1.1 rules for that input.
The migration strategy depends on the direction. A file moving from a 1.1 parser to a 1.2 parser needs every yes/no/on/off value audited: if the intent was a boolean, replace with true/false; if the intent was a string, quote it. A file moving from 1.2 to 1.1 needs the opposite: quoted strings that are intended as booleans must be unquoted, or the 1.1 parser will read them as strings.
The safer pattern is to write files that mean the same thing under both versions: use only true and false for booleans, quote ambiguous strings, and avoid base-60 numbers and leading-zero octals. A file that follows these rules parses identically under 1.1 and 1.2, and the version directive becomes documentation rather than a behavioral switch.
Complete Example Session
# ============================================
# PART 1: THE BOOLEAN DIVERGENCE
# ============================================
# File content:
# enabled: yes
# mode: on
# country: NO
# YAML 1.1 parser result:
# { enabled: true, mode: true, country: false }
# YAML 1.2 parser result:
# { enabled: "yes", mode: "on", country: "NO" }
# ============================================
# PART 2: THE WORKFLOW TRIGGER BUG
# ============================================
# GitHub Actions workflow:
# on:
# push:
# branches: [main]
# Python yaml.safe_load result:
# { True: { "push": { "branches": ["main"] } } }
# The key is boolean True, not string "on"
# ============================================
# PART 3: THE VERSION DIRECTIVE IS NOT A SWITCH
# ============================================
# %YAML 1.2
# ---
# enabled: yes
# PyYAML still parses "yes" as boolean because its resolver is 1.1
# ============================================
# PART 4: MIGRATION TO CROSS-VERSION SAFETY
# ============================================
# BEFORE (ambiguous):
# enabled: yes
# mode: on
# version: 1.10
# AFTER (safe under both):
enabled: true # explicit boolean
mode: "on" # quoted string
version: "1.10" # quoted string
# ============================================
# PART 5: PARSER DETECTION TEST
# ============================================
# Parse "key: yes" and check typeof value
# boolean โ 1.1 rules
# string โ 1.2 rules
The parts covered the boolean divergence, the workflow trigger bug, the directive’s lack of enforcement, the cross-version safe rewrite, and the parser detection test.
Quick Reference
Boolean Literals
| Literal | YAML 1.1 | YAML 1.2 |
|---|---|---|
true / false | boolean | boolean |
yes / no | boolean | string |
on / off | boolean | string |
y / n | boolean | string |
Number Changes
| Format | YAML 1.1 | YAML 1.2 |
|---|---|---|
Base-60 (60:00) | integer 3600 | string (removed) |
Octal (0777) | octal | deprecated |
Octal (0o777) | not standard | octal |
Parser Behavior
| Parser | Version | yes Type |
|---|---|---|
PyYAML safe_load | 1.1-derived | boolean |
goccy/go-yaml | 1.2 | string |
yaml.v3 (Go) | 1.2 + compat | boolean if target bool |
yaml (JS v2+) | 1.2 | string |
Best Practices
โ Do This:
# Use explicit booleans
enabled: true # โ
# Quote strings that look like booleans
mode: "on" # โ
# Test the parser's version before relying on behavior
# parse "key: yes" and check the type # โ
โ Don’t Do This:
# Rely on yes/no as booleans across parsers
enabled: yes # โ
# Assume %YAML 1.2 changes the parser's resolver
# %YAML 1.2
# enabled: yes # still boolean in PyYAML # โ
# Trust Python yaml.safe_load for GitHub Actions validation
# (it misreads the on: key) # โ
Common Pitfalls
| Pitfall | Why It Happens | Fix |
|---|---|---|
on: key becomes True | YAML 1.1 boolean resolver | Use a 1.2 parser or quote the key |
%YAML 1.2 has no effect | Directive is advisory | Change the parser |
| Base-60 number parsed as string | Removed in 1.2 | Quote or restructure |
| Octal parse differs | 1.1 0777 vs 1.2 0o777 | Use quoted string for modes |
Real-World Examples
1. Explicit boolean
enabled: true
2. Quoted boolean-like string
mode: "on"
3. Workflow trigger key
"on":
push:
branches: [main]
4. Parser detection
# yaml.safe_load("key: yes") โ True (1.1)
5. Go 1.2 parser
// goccy/go-yaml: "on" stays string
6. Base-60 removal
time: "60:00" # quote if string intended
7. Octal quote
mode: "0755"
8. Version directive
%YAML 1.2
---
9. Norway problem
country: "NO"
10. Migration audit
grep -rn "yes\|no\|on\|off" configs/
Visual
Boolean Resolution
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ SAME TEXT, DIFFERENT TYPE โ
โ โ
โ enabled: yes โ
โ โ โ
โ โโโโโโดโโโโโ โ
โ โผ โผ โ
โ YAML 1.1 YAML 1.2 โ
โ โ โ โ
โ โผ โผ โ
โ true "yes" โ
โ (boolean) (string) โ
โ โ
โ Only true/false are booleans in 1.2 โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
The Workflow Trigger
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ on: โ
โ push: โ
โ โ
โ PyYAML (1.1): โ
โ { True: { push: ... } } โโโ key is boolean โ
โ โ
โ goccy/go-yaml (1.2): โ
โ { "on": { push: ... } } โโโ key is string โ
โ โ
โ GitHub Actions uses 1.2; Python validator is wrong โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Directive vs Resolver
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ %YAML 1.2 โ
โ --- โ
โ enabled: yes โ
โ โ
โ Parser reads directive โ
โ โ โ
โ โผ โ
โ Does parser switch resolver? โ
โ โ โ
โ โโโโโโดโโโโโ โ
โ โผ โผ โ
โ PyYAML goccy โ
โ โ โ โ
โ โผ โผ โ
โ No switch 1.2 by default โ
โ yes = true yes = "yes" โ
โ โ
โ The implementation decides, not the directive โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Cross-Version Safe Writing
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ WRITE FILES THAT PARSE THE SAME EVERYWHERE โ
โ โ
โ Booleans: true / false only โ
โ Strings: quote ambiguous values โ
โ Numbers: no base-60, quote octals โ
โ โ
โ enabled: true โ both versions โ
โ mode: "on" โ both versions โ
โ time: "60:00" โ both versions โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Summary
| Item | Value |
|---|---|
| YAML 1.1 booleans | yes/no/on/off/y/n |
| YAML 1.2 booleans | true/false only |
| JSON subset | YAML 1.2 guarantee |
| Base-60 numbers | Removed in 1.2 |
| Octal notation | 0777 (1.1) vs 0o777 (1.2) |
| Version directive | Advisory, not enforced |
| PyYAML | 1.1-derived resolver |
| goccy/go-yaml | 1.2 compliant |
Key takeaways:
- The boolean set is the defining difference. YAML 1.1 treats
yes,no,on, andoffas booleans; YAML 1.2 does not. A file that relies on the 1.1 behavior parses to different data under a 1.2 parser . - The version directive does not switch the parser.
%YAML 1.2documents intent; PyYAML continues to apply its 1.1 resolver regardless . - The
on:key is the canonical failure case. GitHub Actions useson:as a trigger key; a Python validator usingyaml.safe_loadreads it as booleanTrueand reports a false error . - Base-60 numbers and octal notation also changed. YAML 1.2 removed sexagesimal numbers and standardized
0ooctal; parsers vary in what they accept . - Write cross-version safe files. Use only
true/falsefor booleans, quote ambiguous strings, and avoid the removed number formats. A file written this way parses identically under both versions.
Remember: YAML 1.1 and 1.2 are not two revisions of the same rules; they are two rule sets that share a syntax. The text on is a boolean in one and a string in the other, and the version number at the top of the file does not force the parser to care. When a YAML file parses differently than expected, check the parser’s version before checking the file. When writing a file that must work everywhere, use the subset that both versions agree on: true and false, quoted strings, and no base-60 numbers.
Stop using slow, ad-bloated tool sites! ๐คฎ
๐ Search “KandZ Tools” on Google to use many professional utilities for free.
KandZ.me is the ultimate minimalist hub for:
โ
Finance (Mortgage, Interest, Inflation)
โ
Tech (Base64, JSON, Dev Suite, IP)
โ
Health (BMI, BMR, TDEE)
โ
Productivity (Timer, Workspace, QR)
โก๏ธ Fast & Private
๐ No data leaves your device
๐ 100% Free
๐ Use it now: https://tools.kandz.me
๐ Bookmark itโyouโll need it later!