Format & Spec · October 2026

llms.txt Link Hygiene

The structure of an llms.txt file gets reviewed; the links inside it usually do not. We pulled 17 production files on October 19, 2026 and looked at all 2,768 URLs in them — how they are written, where they point, and which of them a fetcher would fail on. The answers split cleanly: almost everyone uses absolute URLs, three quarters point at Markdown twins, and the failures cluster in one predictable place.

What 17 Live Files Do With Their Links

Fetched with a normal desktop user agent, redirects followed, bodies parsed for Markdown links. Two hosts answered without a file — astro.build/llms.txt returned 404 and tailwindcss.com/llms.txt returned 404 with an HTML body — so 17 of 19 candidates were usable. Totals across those 17: 394,235 bytes and 2,768 links.

File Links Absolute Root-relative .md Trailing /
docs.stripe.com52752705160
docs.anthropic.com78478407830
nuxt.com34234203370
bun.sh32232203190
docs.github.com117117000
developers.cloudflare.com112112000
ui.shadcn.com111111000
hono.dev9494000
www.checklyhq.com68680159
www.prisma.io64640291
resend.com58580250
vite.dev49643470
supabase.com42420300
www.mintlify.com41410350
vercel.com27270200
svelte.dev77000
llmstxt.org33030

Three independent decisions sit in those columns — URL form, Markdown twin or HTML page, trailing slash — and only the first is near-unanimous.

Absolute or Root-Relative: One File Buys the Risk

2,725 of 2,768 links are absolute, 98.4%. The single exception is vite.dev, where 43 of 49 links are written as site-root paths such as /guide/features.md, and only the two external references to developer.mozilla.org and rolldown.rs carry a host. That works because the file sits at the domain root, so every path resolves back to vite.dev. Six of its twins we sampled answered 200.

The trap is what happens when the same convention moves into a subdirectory. A relative path in Markdown is resolved against the containing document's URL, and the answer differs by one directory level:

# file at https://example.com/llms.txt
#   link: ./guide/install.md  →  https://example.com/guide/install.md   (correct)

# file at https://example.com/docs/llms.txt
#   link: ./guide/install.md  →  https://example.com/docs/guide/install.md   (correct)
#   link: /guide/install.md   →  https://example.com/guide/install.md   (silently wrong)

Both parse; the failure is silent. If your index is one of several — the spec allows /docs/llms.txt alongside the root file — that ambiguity costs you nothing to remove. Absolute URLs also survive the case where an agent has cached the file but resolves links against a different base, which is what happens when a proxy or a mirror serves your content under its own hostname.

The .md Twin Majority, and the Index Pages That 404

2,145 links (77.5%) end in .md. Nine of the 17 files are almost entirely twins — Stripe at 516 of 527, Anthropic at 783 of 784, Nuxt at 337 of 342 — while five files link none at all: Cloudflare, GitHub, Svelte, Hono and shadcn all point at HTML pages only. Both choices are defensible; the twin is smaller and cleaner, the HTML page is guaranteed to exist.

We sampled 12 twins across 11 files and 11 returned 200. The content type was text/markdown; charset=utf-8 in ten cases and text/plain; charset=utf-8 for Prisma's /index.md. The one failure is instructive: bun.com/docs/index.md returned 404 with an HTML body, because an index.md is a directory of links rather than a document. In a 12-link sample from bun's file, the two section-index twins — /docs/index.md and /docs/runtime/index.md — both 404'd while the ten leaf pages served normally. Section indexes are the twin that generators forget to emit.

One more property of twins worth knowing: the same twin URL is not always the same document. Stripe's /sandboxes.md sends Vary: Accept, Accept-Language and returns # Sandboxes (3,731 bytes) to an en-US client, # サンドボックス (2,463 bytes) to ja-JP, and # 沙盒 (3,560 bytes) to zh-CN. That is correct behaviour for a localized site, and it means an English llms.txt entry can hand an agent a Japanese body if the agent sends a matching language header. Point entries at the localized path when you have one.

Trailing Slashes Are an HTML Signal

Checkly's file is the outlier on URL shape: 59 of its 68 links end in a slash, and the six we sampled all returned 200 as HTML. Its only .md link is /pricing.md. This is coherent, not sloppy — the site publishes no Markdown twins, so every entry is a page — but it matters for anyone copying the pattern. A trailing slash and a .md suffix describe different resources, and a generator that appends .md to a slash-terminated path produces /product/uptime-monitoring/.md, which 404s on most hosts. Pick one convention per file and keep it for every entry; the inconsistency is what breaks parsers that deduplicate or rewrite.

Duplicates, Alias Hosts and Migrated Origins

Counted per file: Stripe lists 27 URLs more than once, one quickstart three times (/payments/quickstart.md) — what happens when a page belongs to several sections and is copied into each. Checkly has 7 duplicates, Resend 2, Vite 1. Duplicates are legal Markdown, harmless to a set-building parser, and they spend tokens to say nothing new.

Two files show what a host change does to an index. docs.anthropic.com/llms.txt and platform.claude.com/llms.txt are byte-identical at 86,059 bytes with 784 links, and all 784 point at platform.claude.com — so the docs host is a pure alias. bun.sh/llms.txt is the other direction: all 322 links point at bun.com, one of them at mintlify.com. Both files are internally consistent, which is the property that matters. The broken version of this pattern is a half-migrated file where some entries still name the retired host and the redirect that used to cover them was dropped.

The Link Check Before You Ship

This is not the delivery check — that is a separate question of content type and caching. This one is about the URLs inside the file, and it takes about a minute:

# 1. every URL in the file, one per line
curl -s https://example.com/llms.txt | grep -oE '\]\([^)]+\)' | tr -d '])('

# 2. count the URL shapes — absolutes, root-relative, .md, trailing slash
curl -s https://example.com/llms.txt | grep -oE '\]\([^)]+\)' \
  | awk '{ if ($0 ~ /^\]\(https?:/) a++; if ($0 ~ /^\]\(\//) r++; if ($0 ~ /\.md\)/) m++; if ($0 ~ /\/\)/) s++ } \
         END { print "abs", a+0, "root", r+0, "md", m+0, "slash", s+0 }'

# 3. status of every unique link (follow redirects)
curl -s https://example.com/llms.txt | grep -oE 'https?://[^)]+' | sort -u \
  | xargs -n1 -P8 curl -sIL -o /dev/null -w "%{http_code} %{url_effective}\n"

# 4. duplicates
curl -s https://example.com/llms.txt | grep -oE 'https?://[^)]+' | sort | uniq -d

# 5. does a twin return Markdown, or an HTML error page with a 404?

Step 3 is the one that finds real breakage, and step 5 is the one that explains it. A 404 that returns HTML is a routing miss; a 200 that returns HTML is a site that never generated twins and a file that was written as if it had. If your index is hand-maintained, keep it in a form you can regenerate from your route list — the llms.txt generator emits absolute URLs by default for exactly this reason — then run the checker and confirm the link count it reports matches the number of pages you meant to list.

None of this ranks anything on its own. It decides whether the file you wrote is the file an agent reads, which is the only thing link hygiene was ever for.