If you have ever let Claude Code generate a few dozen MDX articles in a row, you have probably seen this: the first thirty pieces ship cleanly, and then one day npm run build dies with YAMLException: end of the stream or a document separator is expected. You re-read the body of the article a dozen times before noticing that the real culprit is sitting up in the frontmatter.
I lost half a day to this when I first wired Claude Code into the auto-publishing pipeline behind Dolice Labs, the four AI blogs (Claude Lab, Gemini Lab, Antigravity Lab, Rork Lab) I run as an indie developer. After more than ten years building wallpaper and relaxation apps for the App Store and Google Play — Dolice's apps have crossed 50 million cumulative downloads since 2014, mostly monetized through AdMob — I am used to environment-specific failures, but the way YAML parsers react to LLM output is in a category of its own.
The symptom — your build dies on "YAML parse failed"
On a Next.js + MDX + gray-matter style stack, you tend to see something like this:
[Error: YAMLException: can not read a block mapping entry; a multiline key may not be an implicit key
at line 4, column 1:
description: "Run Claude Code in "Plan mode" to preview…"
^]
Sometimes the wording is unexpected end of the stream, sometimes tag suffix cannot contain flow indicator characters. None of it looks broken in the editor, so the first instinct is to blame the build environment. In practice, almost every occurrence comes down to one thing: there is a character in the frontmatter that the YAML parser cannot interpret the way Claude assumed it would.
When it bites — three patterns Claude steps on the most
These are the three failure modes I have actually seen in production. Claude Code reaches for quotation marks when it wants to sound natural, and that is where the trouble lives.
Pattern 1: a double-quoted value with a double quote inside
description: "Run Claude Code in "Plan mode" before applying changes…"Syntactically the parser stops at the second ", then treats the rest as a stray key. To embed a literal " inside a double-quoted YAML scalar you have to write \". Claude rarely escapes proactively because, linguistically, it is just writing the next sensible sentence.
Pattern 2: a bare colon followed by a space, mid-value
title: "Claude Code 3.0: Release Highlights"The quoting saves you here, but the moment Claude forgets the outer quotes — for example writing title: 3.0: Highlights — the parser sees two key: value pairs and falls over. Long descriptions with mismatched quotes amplify the problem because the parser keeps consuming downstream lines looking for the missing closing quote.
Pattern 3: a tag array element that contains a comma
tags: ["Claude Code, Cowork", "TypeScript"]This is technically valid YAML, but it silently breaks any downstream tags.includes("Claude Code") filter. No build error, no warning — just a tag page that mysteriously refuses to list the article. That is the worst kind of bug because no log will ever tell you about it.
Why this happens — quoting rules YAML inherited from C
In YAML 1.2, a double-quoted scalar uses C-style escaping. To embed a literal " you must write \". Single-quoted scalars use a different convention: you escape a single quote by doubling it ('') and everything else is taken literally. Claude has both rules in its training data, but it cannot reliably tell which convention applies at the moment it is writing the next token, so it defaults to whatever sounds natural in prose.
The fix is not to make Claude obey YAML escaping. The fix is to narrow the surface where it can possibly go wrong.
Five patterns I trust in production
In Dolice Labs I publish multiple articles per site per day across four sites, fully driven by Claude Code. After getting burned a few times, I settled on these five conventions, ordered from most reliable to most flexible.
Fix 1: never use straight double quotes inside a quoted scalar
For Japanese articles I instruct Claude to substitute 「」 (full-width corner brackets) whenever it wants to quote something inside a frontmatter value. For English I tell it to swap straight double quotes for curly quotes (" ") or single quotes. Either way the parser never sees an ambiguous " inside another ".
description: "Run Claude Code in 'Plan mode' before applying changes"Fix 2: switch the outer wrapper to single quotes
If you genuinely need straight double quotes in the value, wrap the entire scalar in single quotes.
description: 'Run Claude Code in "Plan mode" before applying changes'To include a literal ' inside, double it (''). For a site like ours with parallel Japanese and English versions, the simplest rule is "JA uses corner brackets, EN uses single-quoted outer scalar." That keeps the convention consistent across translators.
Fix 3: use a block scalar (| or >)
When a description is long, or includes multiple quoted phrases, block scalars are the cleanest option:
description: >
Run Claude Code in "Plan mode" to dry-run the change.
This is why the official docs keep repeating that the
Edit tool is destructive.> folds line breaks into spaces; | preserves them. For Open Graph descriptions I prefer > because the final value ends up on one line in the HTML head.
Fix 4: avoid colon + space mid-value
When you want a colon-like break in a title or description, prefer an em dash or a full-width colon over : followed by a space.
# Risky
title: "Claude Code 3.0: Release Highlights"
# Safer
title: "Claude Code 3.0 — Release Highlights"Fix 5: no commas inside a tag value
One tag, one concept. Resist the temptation to write "Claude Code, Cowork" as a single tag for the sake of brevity. Split them, and let your filter handle the OR condition.
tags: ["Claude Code", "Cowork", "TypeScript"]Prevention — validate the frontmatter before push
At Dolice Labs we run a tiny Python script over every staged MDX before git push. It carves out the frontmatter and feeds it to yaml.safe_load. If anything throws, the push aborts long before Cloudflare ever sees the commit.
import re
import sys
import yaml
from pathlib import Path
def check(path):
text = Path(path).read_text(encoding="utf-8")
m = re.match(r"^---\n(.*?)\n---\n", text, re.DOTALL)
if not m:
return f"{path}: no frontmatter"
try:
yaml.safe_load(m.group(1))
except yaml.YAMLError as e:
return f"{path}: {e}"
return None
errors = [e for e in (check(p) for p in sys.argv[1:]) if e]
if errors:
print("\n".join(errors))
sys.exit(1)Wire this into your push step (python3 yaml_check.py content/articles/**/*.mdx) and the failure mode collapses from "Cloudflare build error fifteen minutes later" into "line and column printed locally in under a second."
The lesson — narrow the surface, do not police the LLM
What I learned, after several Cloudflare builds died in a row, is that asking an LLM to follow YAML escaping rules perfectly is the wrong battle. It is far cheaper to narrow the set of characters Claude is allowed to use in frontmatter values, and validate mechanically before push. Corner brackets instead of double quotes, em dashes instead of colons, no commas inside tags — those three rules, plus a 30-line Python validator, ended the issue for me across all four Dolice Labs sites. Thanks for reading.