Skip to content

content:lint --published reports "passed" on a non-mapping frontmatter that then crashes astro build at config load #206

Description

@hotlong

The finding

Discovered by the #162 behavioural-equivalence table, which fed pathological frontmatter through every reader. This is pre-existing — it reproduces identically at main (8a36292) and at the #162 head, so #162 neither introduced nor fixed it. #162 was a pure consolidation and was not allowed to change behaviour, so it is filed here instead.

A post whose frontmatter is valid YAML but not a mapping — a bare scalar or a list — is treated differently by the two readers, and the disagreement is after the fence split, so consolidating the fence did not touch it.

Reproduction

content/blog/<slug>/index.mdx:

---
just a bare scalar
---

Body.

At 8a36292, with that one file added:

$ pnpm content:lint --published
✓ content lint passed (335 files, 44 glossary terms checked)      # exit 0

$ node -e "import('./astro.config.mjs')"
Error: <slug>/index.mdx has empty or non-object frontmatter        # exit 1

A YAML list (- one\n- two) behaves the same way.

Why the two disagree

  • scripts/content-lint.mjs does data = yaml.load(raw) ?? {}. A bare string is truthy, so data becomes the string — every data[key] lookup is then undefined. In default mode that surfaces as six Missing required frontmatter field errors (so the file is caught). In --published mode the loop hits if (onlyPublished && data.status !== 'published') continue; first, undefined !== 'published', and the file is skipped entirely — the gate never looks at it.
  • scripts/lib/post-dates.mjs rejects it outright: if (!data || typeof data !== 'object') throw. astro.config.mjs calls readPostLastmods() at module scope to build the sitemap lastmod map, so the throw lands at config load.

Why it is worth a card

Nothing ships broken — pnpm build runs content:lint --published and then astro build, so the bad file does stop the build. But it stops it in the wrong place and with the wrong message:

  • the gate whose job is to catch bad frontmatter prints ✓ content lint passed, which is a false statement about that file;
  • the actual failure arrives from astro.config.mjs during config load, before Astro reports any file context of its own, so an author sees a config-time stack rather than a lint error against their post;
  • and the gate's own contract is "a run that measured nothing is a failure, not a pass" (the --dist mode says so in a comment). --published silently measuring nothing for this file is the same shape.

Options

  1. Reject a non-mapping frontmatter in content-lint.mjs, matching post-dates.mjs — one added check before the --published skip, wording this script's own. Smallest fix; makes the gate the thing that reports it.
  2. Move the mapping check into scripts/lib/frontmatter.mjs as a third failure reason, so both readers get it from one place. Larger: the helper currently does no YAML at all by design, and content-lint.mjs would need its own wording for the new reason.
  3. Leave it — the build does fail, just later and less legibly.

Option 1 looks right, and it needs its own equivalence run (the #162 harness covers exactly these cases).

Found while implementing #162.

Generated by Claude Code

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions