Skip to content

[finding] Nothing local parses docs frontmatter — an unquoted description containing a colon reaches Build Docs before anything says so #10493

Description

@os-elon

Found the hard way on #10215 / PR #10333: a new page landed with a frontmatter description that is an unquoted YAML scalar containing apis: . YAML reads the colon-plus-space as the start of a nested mapping, so the page's frontmatter does not parse:

./content/docs/api/declarative-endpoints.mdx
YAMLParseError: Nested mappings are not allowed in compact mappings at line 2, column 14:
description: Expose your app to systems outside the platform by declaring an ap…
^

Every local gate the card named passed on that exact file — check:doc-anchors, check:doc-authoring, check:docs-audit-scope, check:docs-redirects, check:role-word, check:published-readme-links. The only thing that caught it was Build Docs (ci.yml, pnpm --filter @objectstack/docs build), a full next build on a 30-minute-timeout runner that exists only in CI.

Why nothing local sees it, measured rather than assumed

  • No script under scripts/ or scripts/docs-audit/ YAML-parses content/docs/** frontmatter. The seven scripts that import yaml parse workflows, changesets and pnpm-workspace.yaml — none of them docs frontmatter.
  • The one gate that reads these files line by line deliberately avoids it: scripts/check-doc-anchors.mjs carries a helper whose docblock is "Blank out a leading YAML frontmatter block, preserving line count." It is right to do so for its own purpose (heading ids), but it means the closest thing to a frontmatter reader is the thing that erases it.

So the invariant "every content/docs/**/*.mdx has parseable frontmatter" has no owner below the full site build.

Why it is worth a gate rather than a shrug

  1. The failure is deterministic and the fix is one character-class — quoting the scalar. Paying for it with a next build on the merge gate is the whole cost of the loop for a defect a 30ms parse names exactly.
  2. The trigger is ordinary prose. Any description mentioning a metadata key by name (apis:, fields:, views:) or writing any word: word reproduces it. Documentation about a metadata platform is the corpus most likely to write a colon.
  3. Build Docs is path-filtered (needs.filter.outputs.docs). It is the right gate for the real build, but it is late and coarse; the parse is neither.
  4. It would land green and empty. Measured just now with the same yaml 2.9.0 the docs build resolves: 396/396content/docs/**/*.mdx frontmatter blocks parse clean. There is no backlog to grandfather and no baseline to carry — the same "cheapest possible moment to take a directory in" argument check-doc-authoring's own header makes about docs/.

Same shape as #7484 (a documentation invariant that CI reported [200] OK on until a script was written for it), which is the precedent for how this repo closes this class.

Sketch

A check:doc-frontmatter that walks the same population affected-docs.mjs --all already defines (content/docs/**/*.mdx minus references/**), parses the leading --- block with yaml, and fails naming the file, the line and the parser's own message. Worth deciding at the same time whether it should assert the two keys every page carries (title, description) are present and are strings, since a missing title is the neighbouring silent defect — that part is a judgement call, the parse itself is not.

Not fixed on #10215 — that card is documentation-only and this is a scripts/ + workflow change. Unassigned for triage.

Metadata

Metadata

Assignees

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions