| | |

YAML 10 ๐Ÿ“„ YAML 1.2 vs YAML 1.1 Spec Differences

โฑ๏ธ Estimated read: 9 min | |

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.

ParserYAML Versionyes
PyYAML safe_load1.1-derivedboolean
Go goccy/go-yaml1.2string
Go yaml.v31.2 with 1.1 compatboolean only when target is bool
JavaScript yaml v2+1.2string
Ruby Psych (2.6+)1.2string

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

LiteralYAML 1.1YAML 1.2
true / falsebooleanboolean
yes / nobooleanstring
on / offbooleanstring
y / nbooleanstring

Number Changes

FormatYAML 1.1YAML 1.2
Base-60 (60:00)integer 3600string (removed)
Octal (0777)octaldeprecated
Octal (0o777)not standardoctal

Parser Behavior

ParserVersionyes Type
PyYAML safe_load1.1-derivedboolean
goccy/go-yaml1.2string
yaml.v3 (Go)1.2 + compatboolean if target bool
yaml (JS v2+)1.2string

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

PitfallWhy It HappensFix
on: key becomes TrueYAML 1.1 boolean resolverUse a 1.2 parser or quote the key
%YAML 1.2 has no effectDirective is advisoryChange the parser
Base-60 number parsed as stringRemoved in 1.2Quote or restructure
Octal parse differs1.1 0777 vs 1.2 0o777Use 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

ItemValue
YAML 1.1 booleansyes/no/on/off/y/n
YAML 1.2 booleanstrue/false only
JSON subsetYAML 1.2 guarantee
Base-60 numbersRemoved in 1.2
Octal notation0777 (1.1) vs 0o777 (1.2)
Version directiveAdvisory, not enforced
PyYAML1.1-derived resolver
goccy/go-yaml1.2 compliant

Key takeaways:

  • The boolean set is the defining difference. YAML 1.1 treats yes, no, on, and off as 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.2 documents intent; PyYAML continues to apply its 1.1 resolver regardless .
  • The on: key is the canonical failure case. GitHub Actions uses on: as a trigger key; a Python validator using yaml.safe_load reads it as boolean True and reports a false error .
  • Base-60 numbers and octal notation also changed. YAML 1.2 removed sexagesimal numbers and standardized 0o octal; parsers vary in what they accept .
  • Write cross-version safe files. Use only true/false for 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.

Was this helpful?


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!