# YAML errors explained: the ones that break your build — and the ones that never error at all > Real parser output for the five YAML mistakes that fail CI (tabs, unclosed quotes, bad flow, bad indent), plus six values YAML silently mangles — NO becomes false, 12:30:45 becomes 45045. All verified. - Canonical page: https://obelisk.club/blog/yaml-errors-explained - Author: Peter Zhang - Published: 2026-09-17 - Tags: yaml, devops, ci, docker-compose, github-actions, debugging YAML failures come in two kinds. The loud kind stops your `docker compose up` or GitHub Actions run with a parser error and a line number. The quiet kind parses fine, deploys fine, and then your Norwegian users are in country `false`. We re-verified everything below against a real parser (PyYAML 6, the engine behind an enormous amount of CI tooling) while writing this — every error message and every mangled value is actual output, not paraphrase. To check a file without installing anything, [Yamlite](https://obelisk.club/yamlite) (an app I make) validates YAML on-device on iPhone and iPad and shows errors with line and column. ## The loud failures: what the error actually means ### "mapping values are not allowed here" ```text ScannerError: mapping values are not allowed here in "a.yaml", line 1, column 13 ``` The most common YAML error. The value itself contains a colon followed by a space, so the parser reads a second key: `title: Error: something broke` fails at column 13, the second colon. A trailing colon does it too: `pw: abc:` fails at column 8, which is why passwords and Windows paths trip it. A colon with no space after it is fine (`url: http://x.com` parses). Fix: quote the value, `title: "Error: something broke"`, `pw: "abc:"`. In go-yaml the same mistake reads `yaml: line 2: mapping values are not allowed in this context`. ### "expected , but found ''" ```text ParserError: while parsing a block mapping in "c.yaml", line 1, column 1 expected , but found '' in "c.yaml", line 3, column 2 ``` A key is indented differently from its siblings: `b:` at two spaces, `c:` at one. The second location (line 3, column 2) is the key that drifted; the first is only where the enclosing mapping began. go-yaml calls this `did not find expected key`. ### "found character '\t' that cannot start any token" ```text ScannerError: while scanning for the next token found character '\t' that cannot start any token in "", line 2, column 1 ``` You indented with a Tab. YAML forbids tabs in indentation, full stop. This is the classic paste-from-terminal failure: your editor shows what looks like clean nesting, but line 2 column 1 holds `\t`. Fix: convert tabs to spaces (2 is the convention), and turn on "show invisibles" for YAML files. ### "while scanning a quoted scalar … found unexpected end of stream" ```text ScannerError: while scanning a quoted scalar in "", line 1, column 11 found unexpected end of stream ``` An opened `"` was never closed. The parser reads to the end of the file looking for the closing quote, so the reported line is where the quote *opened* — the actual missing character is usually much further down. Fix from the reported column, not from the end of the file. ### "while parsing a flow sequence … expected ',' or ']'" ```text ParserError: while parsing a flow sequence in "", line 1, column 8 expected ',' or ']', but got ':' ``` A flow collection (`[80, 443` or `{key: value`) was never closed before the next mapping started. Common in `ports:` lists and inline env maps. ### "while parsing a block collection … expected " ```text ParserError: while parsing a block collection in "", line 2, column 3 expected , but found '' ``` Two sibling list items with different indentation — `- run: build` at two spaces, `- run: test` at three. The parser thinks the first list ended and something new began. This is the most common hand-edited GitHub Actions failure, because workflow files nest four levels deep and one list item drifts by a single space. ### The same mistakes in go-yaml Go programs that read YAML with go-yaml word the errors differently and add only a line number. I fed the same broken files to PyYAML 6.0.3 and go-yaml v3.0.1: | Mistake | PyYAML | go-yaml v3 | | --- | --- | --- | | Colon and space inside a value | `mapping values are not allowed here` | `mapping values are not allowed in this context` | | Key indented differently from its siblings | `expected , but found ''` | `did not find expected key` | | List item indented differently | `expected , but found ''` | `did not find expected '-' indicator` | | Tab used for indentation | `found character '\t' that cannot start any token` | `found character that cannot start any token`, or `found a tab character that violates indentation` | | Unclosed double quote | `while scanning a quoted scalar … found unexpected end of stream` | `found unexpected end of stream` | Don't trust go-yaml's line number blindly. For a tab on line 3 it reported line 2; for a key mis-indented on line 5 it reported line 1, for a list item on line 3 it reported line 1, and for a colon on line 1 it gave no line at all. Look a few lines below the reported one, or run the file through a second parser to get a line and column. ## The quiet failures: valid YAML, wrong data These all parse **without any error**. We ran each one; the arrow shows what your program actually receives: | You wrote | YAML delivers | Why | | --- | --- | --- | | `country: NO` | `false` | The Norway problem — YAML 1.1 reads NO/no/Yes/on/off as booleans | | `answer: yes` | `true` | Same boolean coercion | | `version: 1.10` | `1.1` | Unquoted, so it's a float — trailing zero gone | | `time: 12:30:45` | `45045` | Sexagesimal (base-60) notation: 12×3600+30×60+45 | | `zip: 011234` | `4764` | Leading zero → octal | | `port: 80` then `port: 8080` | `8080` only | Duplicate keys: last silently wins, first is discarded | The fix for the first five is the same: **quote any scalar that must stay a string** (`country: "NO"`, `version: "1.10"`). The duplicate-key case is nastier — no quoting saves you, and a merge conflict resolved sloppily can leave two `port:` lines that both look right. A linter that flags duplicates is the only reliable catch. ## Checking YAML before it reaches CI - **On a Mac**, the fastest offline check is Python's parser: `python3 -c "import yaml,sys; yaml.safe_load(sys.stdin)" < config.yml` — errors print with line and column. `yamllint` (via Homebrew) adds style checks and duplicate-key detection. - **On an iPhone or iPad** — reviewing a PR from your phone, editing a config over SSH — [Yamlite](https://obelisk.club/yamlite) does the same job on-device: paste, and errors appear with line and column instantly, including the duplicate keys and tab indentation cases above. Fully offline, nothing uploaded — which matters when the file you're validating is a production config. - **In a browser**, online validators work but mean pasting your config — often containing secrets and internal hostnames — into someone else's server. For anything sensitive, keep validation local. ## FAQ **Why does YAML turn NO into false?** YAML 1.1 defines yes/no/on/off/true/false (any case) as booleans. Quote it (`"NO"`) to keep the string. YAML 1.2 dropped this, but most parsers in the wild still speak 1.1. **Are duplicate keys an error in YAML?** The spec says keys must be unique, but most parsers accept duplicates and keep the last one silently. Use a linter that flags them — the parser won't. **Why does my YAML fail with a tab error when I can't see any tabs?** The tab is invisible. Re-indent the reported line with spaces, or run the file through an editor's convert-tabs command. **What's the fastest way to validate YAML on iPhone?** An on-device validator like [Yamlite](https://obelisk.club/yamlite) — paste the file, read the line/column errors. No upload involved. --- *Config files travel with the rest of your toolchain — if you're batch-fixing filenames before a deploy, the same care-first instinct applies to [batch renaming on Mac](https://obelisk.club/blog/batch-rename-files-mac).*