Found while implementing #178. Pre-existing on main — the counts below are byte-identical before and after that change, which is how it was spotted.
Notation. This tracker's body sanitizer decodes HTML numeric character references, including inside code fences — the first version of this card was rewritten by it into one where the before and after examples read identically. So throughout this card, AMP stands for one literal ampersand character. AMP#x2A; means the six characters that would otherwise be typed as an ampersand, hash, x, 2, A, semicolon. That behaviour is itself worth knowing about when filing evidence here.
What is on main
The body served at /llms-full.txt contains HTML numeric character references, in text that is not HTML:
| entity |
character it encodes |
occurrences |
AMP#x2A; |
asterisk |
58 |
AMP#x60; |
backtick |
5 |
AMP#x29; |
closing parenthesis |
4 |
They come from page.data.getText('processed'), via getLLMText in apps/docs/lib/source.ts.
The authored MDX is plain. content/docs/build/index.mdx:36:
1. **Just exploring?** → Open ObjectOS, start the [AI Builder](/docs/build/ai-builder),
say *"I need to track customer support tickets ..."*
The served text for that same line, with the entities spelled in the notation above:
1. **Just exploring?** → Open ObjectOS, start the [AI Builder](/docs/build/ai-builderAMP#x29;,
say AMP#x2A;"I need to track customer support tickets ..."*
Two things happened: the closing parenthesis of the link target became AMP#x29;, and the opening asterisk of the emphasis pair became AMP#x2A; while its closing partner stayed a literal asterisk.
Why it is worth a card
Two effects, one cosmetic and one not.
Cosmetic: an LLM reading this file reads AMP#x2A; literally. It is not markdown emphasis and it is not an asterisk; it is six characters of noise inside a sentence, 58 times. The whole file exists to be read as text by something that will not run it through an HTML parser.
Not cosmetic: where the escaped character is a link's closing parenthesis, the markdown link stops being a link. Two of the four AMP#x29; occurrences do exactly that, both on [AI Builder](/docs/build/ai-builder... — one in content/docs/build/index.mdx, one in content/docs/build/ai-builder.
Both render correctly on the website, because there the entity decodes back to a parenthesis before markdown ever sees it. In llms-full.txt nothing decodes it, so the link is delivered as broken literal text.
These are the only 2 of the 646 link targets in the served body that are not well-formed; the other 644 are fine.
Measured on main @ 6653744, production build, fetched over HTTP.
Shape of the fix
Not obvious enough to prescribe, which is why this is a finding rather than a task. getText('processed') is fumadocs' own pipeline and the escaping happens inside it. Candidates: ask for a different text mode; decode the numeric references on the way out in getLLMText; report it upstream. Worth checking which characters fumadocs escapes and why before picking — a blind decode of every numeric reference would also rewrite one an author typed on purpose.
/llms.txt is unaffected: it emits titles and descriptions from frontmatter, not processed body text.
Out of scope for #178, which changes how URLs in these bodies are spelled and leaves the 2 malformed targets exactly as it found them. #178 is not addressed here.
Reproduce
cd apps/docs && pnpm run build
grep -o 'AMP#x[0-9A-Fa-f][0-9A-Fa-f];' .next/server/app/llms-full.txt.body | sort | uniq -c
(with AMP typed as a literal ampersand)
Generated by Claude Code
Found while implementing #178. Pre-existing on
main— the counts below are byte-identical before and after that change, which is how it was spotted.What is on main
The body served at
/llms-full.txtcontains HTML numeric character references, in text that is not HTML:AMP#x2A;AMP#x60;AMP#x29;They come from
page.data.getText('processed'), viagetLLMTextinapps/docs/lib/source.ts.The authored MDX is plain.
content/docs/build/index.mdx:36:The served text for that same line, with the entities spelled in the notation above:
Two things happened: the closing parenthesis of the link target became
AMP#x29;, and the opening asterisk of the emphasis pair becameAMP#x2A;while its closing partner stayed a literal asterisk.Why it is worth a card
Two effects, one cosmetic and one not.
Cosmetic: an LLM reading this file reads
AMP#x2A;literally. It is not markdown emphasis and it is not an asterisk; it is six characters of noise inside a sentence, 58 times. The whole file exists to be read as text by something that will not run it through an HTML parser.Not cosmetic: where the escaped character is a link's closing parenthesis, the markdown link stops being a link. Two of the four
AMP#x29;occurrences do exactly that, both on[AI Builder](/docs/build/ai-builder...— one incontent/docs/build/index.mdx, one incontent/docs/build/ai-builder.Both render correctly on the website, because there the entity decodes back to a parenthesis before markdown ever sees it. In
llms-full.txtnothing decodes it, so the link is delivered as broken literal text.These are the only 2 of the 646 link targets in the served body that are not well-formed; the other 644 are fine.
Measured on
main@6653744, production build, fetched over HTTP.Shape of the fix
Not obvious enough to prescribe, which is why this is a finding rather than a task.
getText('processed')is fumadocs' own pipeline and the escaping happens inside it. Candidates: ask for a different text mode; decode the numeric references on the way out ingetLLMText; report it upstream. Worth checking which characters fumadocs escapes and why before picking — a blind decode of every numeric reference would also rewrite one an author typed on purpose./llms.txtis unaffected: it emits titles and descriptions from frontmatter, not processed body text.Out of scope for #178, which changes how URLs in these bodies are spelled and leaves the 2 malformed targets exactly as it found them. #178 is not addressed here.
Reproduce
(with
AMPtyped as a literal ampersand)Generated by Claude Code