Skip to content

🐛 FIX: Make a document's own front matter available in env.metadata#1171

Open
holmboe wants to merge 1 commit into
executablebooks:masterfrom
holmboe:fix-947-own-frontmatter-metadata
Open

🐛 FIX: Make a document's own front matter available in env.metadata#1171
holmboe wants to merge 1 commit into
executablebooks:masterfrom
holmboe:fix-947-own-frontmatter-metadata

Conversation

@holmboe

@holmboe holmboe commented Jul 19, 2026

Copy link
Copy Markdown

Closes #947

Problem

Sphinx's MetadataCollector only populates env.metadata on the doctree-read event, i.e. after the whole document has been parsed, whereas MyST substitutions are evaluated during the parse. A document could therefore read other documents' front matter via env.metadata["other"]["key"], but its own entry was still empty, so {{ env.metadata[env.docname]["key"] }} failed with an UndefinedError:

---
last_review_date: 1970-09-08
---
The last review date of _this_ file was on {{ env.metadata[env.docname]["last_review_date"] }}

Fix

The renderer now eagerly seeds env.metadata[docname] when the front matter token is rendered (the first token of the document), so the rest of the parse can see it. This also benefits anything else reading env.metadata mid-parse (directives, eval-rst, myst-nb).

Values are read back off the already-built field list rather than re-stringified from the raw YAML, so they are identical to what the collector stores later at doctree-read (which then harmlessly overwrites them). Three details needed to mirror the collector exactly:

  • bibliographic field names are canonicalized via the docutils language module (the collector keys them by node class name after the DocInfo transform);
  • dedication/abstract are skipped — docutils moves them outside the docinfo, so the collector never stores them (without the skip they would leak into env.metadata permanently, caught by the existing test_basic);
  • tocdepth is cast to int, as the collector does.

Scope note

The linked gist demonstrates two distinct problems. This PR fixes the self-reference case. Reading another document's metadata (env.metadata["other-doc"]["key"]) remains inherently order-dependent (alphabetical read order, parallel-read partitioning, stale values on incremental builds since no dependency is recorded) — that would need post-read aggregation, a different feature. The docs update adds a warning admonition documenting this limitation.

Testing

  • New Sphinx build test test_frontmatter_metadata with doctree regressions; asserts the exact env.metadata contents (string values, int tocdepth) and a warning-free build. It fails without the fix.
  • The existing test_basic asserts the full metadata dict for a rich front matter (author/date/dedication/abstract), acting as a contract test that the eager values match the collector's.
  • Full suite passes; verified end-to-end against the reproduction gist from the issue.

AI assistance disclosure

This PR was developed with substantial assistance from Claude Code (Anthropic): root-cause investigation, and drafting of the implementation, tests, and docs. I directed the work and made the design decisions (storing the same stringified values the collector produces, deriving them from the rendered field list so parity holds by construction, and limiting scope to the self-reference bug), and I have reviewed and verified the changes.

🤖 Generated with Claude Code

Sphinx's MetadataCollector only populates env.metadata on the
doctree-read event, i.e. after the whole document has been parsed,
whereas MyST substitutions are evaluated during the parse.
A document could therefore read other documents' front matter via
`env.metadata["other"]["key"]`, but its own entry was still empty,
so `{{ env.metadata[env.docname]["key"] }}` failed.

The renderer now eagerly seeds env.metadata when the front matter
token is rendered (the first token of the document).
Values are read back off the already-built field list, so they are
identical to what the collector stores later: bibliographic field
names are canonicalized via the language module, dedication/abstract
are skipped (docutils moves them outside the docinfo, so the
collector never stores them), and tocdepth is cast to int.

Closes executablebooks#947

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Access Markdown frontmatter with env.metadata

1 participant