Why headings got listed twice
Sätteri’s heading-ids plugin originally pushed each heading straight onto the page’s shared astro.headings array as it walked the document: astro?.headings.push({ depth, slug, text }). That’s safe the first time the plugin runs. It’s not safe the second time, because pushing again appends to whatever the array already contains instead of replacing it.
Starlight, Astro’s own documentation theme, runs its own heading pass to assign IDs before adding anchor links. On a Starlight site, that pass and Sätteri’s own heading-ids plugin could both touch the same page, and the second pass appended duplicate entries instead of producing one clean list. The official changeset for the fix states this directly: “Fixes headings being listed twice in a page’s headings metadata when an integration (such as Starlight) assigns heading IDs with its own heading pass before adding anchor links.”
A default blog was never exposed to this
Nothing about a normal Astro 7 blog build triggers this on its own. It only shows up when a second integration also processes headings before Sätteri’s pass runs, which is a Starlight-shaped setup, not the common case.
The fix
PR #17165, “fix(satteri): Make heading-ids plugin idempotent,” merged 2026-06-23, changes the plugin to collect headings into a local array first, then assign that whole array to astro.headings once, instead of pushing onto the shared array directly.
// BROKEN, appends to whatever astro.headings already contains
astro?.headings.push({ depth, slug, text });// FIXED, local array is built fully, then assigned once
headings.push({ depth, slug, text });
if (astro) astro.headings = headings;Running a version of @astrojs/markdown-satteri that includes this fix is the entire fix. There’s nothing to configure.
Confirmed against this repo’s own installed version
This site’s own node_modules/@astrojs/markdown-satteri@0.3.4 already has the fixed code. Checking the real installed file directly shows the exact after-state from the PR: a local headings array collected first, then assigned once. This site doesn’t use Starlight, so it was never exposed to the duplication itself, but its dependency tree confirms the fix is present in the version this site (and any current Astro 7 install) actually runs.
If you maintain a Starlight site or any integration that assigns heading IDs of its own, confirm @astrojs/markdown-satteri resolves to a version at or after this fix, the same way any other transitive dependency gets checked after a dependency bump.
Browse more fixes from this same upgrade in the Guides & Fixes archive.







