Found during review of the heading-anchors work (present on master since #1158; not introduced by it — the same mechanism affects any validated field).
---
myst:
heading_slug_func: github
---
# Hello World
emits 'str' object is not callable [myst.heading_slug] for every heading, and no slugs are generated (same for any preset name).
Cause
In merge_file_level (myst_parser/config/main.py), the loop calls validate_field(...) — whose validator (check_heading_slug_func) resolves the preset string and sets the callable on the instance — but then follows with setattr(new, name, value), re-assigning the raw string over the validated value. compute_unique_slug later calls the string.
Separately, the field carries "global_only": True metadata, but that is only consumed by _docs.py (documentation generation) — merge_file_level neither respects it nor emits a "not allowed in topmatter" warning, so the user gets a confusing per-heading crash instead of a clear message.
Suggested fix
Two parts, probably one PR:
merge_file_level: don't re-setattr the raw value after validation (use the validator's result / re-read the attribute), so validated fields keep their converted values.
- Enforce
global_only in the topmatter path with a myst.topmatter warning naming the field, which for heading_slug_func (documented as global) is the behaviour users should actually see.
A regression test should cover both: a global-only field in topmatter → warning, and (if any per-document validated-and-converted field exists) that its converted value survives the merge.
Found during review of the heading-anchors work (present on master since #1158; not introduced by it — the same mechanism affects any validated field).
emits
'str' object is not callable [myst.heading_slug]for every heading, and no slugs are generated (same for any preset name).Cause
In
merge_file_level(myst_parser/config/main.py), the loop callsvalidate_field(...)— whose validator (check_heading_slug_func) resolves the preset string and sets the callable on the instance — but then follows withsetattr(new, name, value), re-assigning the raw string over the validated value.compute_unique_sluglater calls the string.Separately, the field carries
"global_only": Truemetadata, but that is only consumed by_docs.py(documentation generation) —merge_file_levelneither respects it nor emits a "not allowed in topmatter" warning, so the user gets a confusing per-heading crash instead of a clear message.Suggested fix
Two parts, probably one PR:
merge_file_level: don't re-setattrthe raw value after validation (use the validator's result / re-read the attribute), so validated fields keep their converted values.global_onlyin the topmatter path with amyst.topmatterwarning naming the field, which forheading_slug_func(documented as global) is the behaviour users should actually see.A regression test should cover both: a global-only field in topmatter → warning, and (if any per-document validated-and-converted field exists) that its converted value survives the merge.