So validieren Sie Ihre llms.txt-Datei
Prüfen Sie Links, Paketnamen und -versionen sowie die Markdown-Struktur in llms.txt mit einem Node.js-Skript ohne Abhängigkeiten und automatisieren Sie Checks in CI.
Um eine llms.txt-Datei zu validieren, prüfen Sie vier Dinge: Sie beginnt mit einer H1, jeder Link führt zu der Seite, die sein Linktext beschreibt, jeder Paketname und jede Version existiert in der Registry, und nichts darin widerspricht Ihrer aktuellen Dokumentation.
Vermutlich hat ein Docs-Tool Ihre llms.txt während eines Launch-Sprints generiert, und seitdem hat niemand sie mehr geöffnet. Inzwischen wurden Seiten umbenannt und Pakete als deprecated markiert. Coding-Agents lesen die Datei trotzdem weiter.
Falls Sie eine Auffrischung zum Format brauchen, beginnen Sie mit unserem Erklärartikel zu llms.txt. Dieser Artikel überspringt die Grundlagen. Er geht die einzelnen Prüfungen durch, liefert ein Node-Skript ohne Abhängigkeiten und zeigt, wie Sie es in der CI ausführen. So werden Probleme erkannt, bevor ein Agent danach handelt.
Die wichtigsten Punkte
- Der ursprüngliche llms.txt-Vorschlag schreibt nur ein einziges Element vor: eine H1 mit dem Namen des Projekts oder der Website. Die Blockquote-Zusammenfassung, Detailabsätze und H2-Linkabschnitte sind allesamt optional.
- Für den llms.txt-Vorschlag gibt es keinen offiziellen Validator. Manche Online-Checker bemängeln eine fehlende Blockquote oder einen fehlenden H2-Abschnitt, obwohl der Vorschlag beides als optional behandelt.
- Ein Paketname in der llms.txt, der in der Registry nicht mehr existiert, kann von jedem registriert werden. Ein Agent mit der Berechtigung, Installationsbefehle auszuführen, lädt dann womöglich alles herunter, was dort veröffentlicht wird.
- Führen Sie die Prüfung bei jedem Deploy aus, um selbst verursachte Fehler zu finden. Lassen Sie sie zusätzlich wöchentlich nach Zeitplan laufen, um Links und Pakete zu erwischen, die upstream kaputtgehen, ohne dass Sie etwas geändert haben.
Warum ist eine veraltete llms.txt ein Supply-Chain-Problem?
Eine veraltete llms.txt ist ein Supply-Chain-Problem, weil Agents die llms.txt als vertrauenswürdiges Referenzmaterial behandeln und oft ohne menschliche Kontrollinstanz danach handeln. Dadurch wird aus einem veralteten Link oder einem falschen Paketnamen eine Anweisung. Ein menschlicher Leser, der auf einen toten Link stößt, gibt auf. Ein Agent mit Shell-Zugriff führt dagegen womöglich npm install für einen Namen aus, der inzwischen jemand anderem gehört.
Das Supply-Chain-Risiko durch llms.txt wurde bereits nachgewiesen. TechRadar berichtete über die Forschung von Alon Hertz, der 6.214 aktive Domains gescannt und dabei 8.265 llms.txt- und llms-full.txt-Dateien gefunden hatte. Nach seiner Zählung verwiesen die Dateien auf 120 verschiedenen Websites auf Pakete oder Domains, die niemand registriert hatte. Anschließend sicherte er sich einige dieser freien Namen und veröffentlichte Pakete, die sich bei ihm meldeten, sobald jemand sie installierte. Laut dem Forscher meldeten sich die Systeme eines Fortune-500-Unternehmens innerhalb einer Stunde. Der Bericht nennt Claude, Codex und Hermes als beteiligte Agents.
Ordnen Sie das realistisch ein. Es handelte sich um ein kontrolliertes Experiment, nicht um dokumentierte Malware in freier Wildbahn. Dennoch ist es ein reales Supply-Chain-Risiko, und ein CI-Job schließt diese Lücke mit geringem Aufwand.
Zugriffskontrolle ist ein separates Thema. Das Blockieren von KI-Crawlern per robots.txt legt fest, welche Teile Ihrer Website Crawler lesen dürfen. Die Validierung der llms.txt stellt sicher, dass das, was sie lesen, noch korrekt ist.
Was Sie bei der Validierung einer llms.txt prüfen sollten
Eine gültige llms.txt hat eine H1 und korrekt formatierte Links. Außerdem verweist jeder Link und jede Paketreferenz auf etwas, das existiert und seiner Beschreibung entspricht. Für den llms.txt-Vorschlag gibt es keinen offiziellen Validator. Die folgenden Prüfungen decken ab, was Online-Checker testen. Zusätzlich enthalten sie Registry-Prüfungen für die Pakete, die in der Datei genannt werden.
Links lösen korrekt auf
Jede URL in der llms.txt sollte nach dem Folgen von Weiterleitungen einen 2xx-Status zurückgeben. Werten Sie 4xx, 5xx und DNS-Fehler als Fehler. Eine Domain, die nicht mehr auflöst, ist das Domain-Gegenstück zu einem nicht registrierten Paket: Jeder kann sie kaufen. Protokollieren Sie auch Weiterleitungen. Eine Weiterleitung, die auf der Startseite landet, bedeutet meist, dass die Seite entfernt wurde. Senden Sie zuerst HEAD und weichen Sie bei Bedarf auf GET aus, da manche Server HEAD ablehnen oder fehlerhaft verarbeiten.
Links liefern, was sie versprechen
Eine 200-Antwort beweist, dass eine URL antwortet. Sie beweist nicht, dass die Seite noch das enthält, was der Linktext verspricht. Prüfen Sie, ob der content-type wie erwartet HTML oder Markdown ist. Vergleichen Sie den <title> oder die erste Überschrift der Seite mit dem Linktext. Achten Sie außerdem auf Soft 404s, bei denen eine Fehlerseite mit Status 200 ausgeliefert wird. Diese Prüfung ist unscharf. Führen Sie sie daher als regelmäßiges Review durch und nicht als Build-Gate.
Pakete und Versionen existieren in der Registry
Prüfen Sie für jedes Paket, das Ihre llms.txt einem Agent zur Installation nennt, ob es in der Registry existiert. Manuell zeigt npm view <name> an, was die Registry für dieses Paket enthält. Gibt es kein solches Paket, bricht der Befehl mit einem Fehler ab. In einem Skript rufen Sie das Paketdokument von registry.npmjs.org ab. Ein Name, den die Registry nicht kennt, ist ein Name, den jemand anderes beanspruchen kann. Bei gepinnten Versionen prüfen Sie, ob genau diese Version in der Liste der veröffentlichten Versionen des Pakets auftaucht. PyPI, crates.io und andere Ökosysteme bieten entsprechende Abfragemöglichkeiten.
Die Markdown-Struktur ist gültig
Der ursprüngliche llms.txt-Vorschlag schreibt nur einen Abschnitt verbindlich vor: eine H1 mit dem Namen des Projekts oder der Website. Abgesehen von einer optionalen Byte Order Mark (BOM) muss die Datei mit # Name beginnen. Laut der aktuellen llms.txt-Spezifikation enthält jeder H2-Abschnitt eine Markdown-Liste, und jeder Eintrag darin muss mit einem [name](url)-Link beginnen. Hinweise nach einem Doppelpunkt sind optional. Lassen Sie den Build bei fehlender H1 oder fehlerhafter Linksyntax fehlschlagen. Bei fehlender Blockquote oder fehlender H2 sollte er dagegen nicht fehlschlagen. Manche Online-Checker bemängeln beides trotzdem. Werten Sie diese Warnungen daher nicht als Verstöße gegen die Spezifikation.
Die Datei widerspricht nicht der aktuellen Dokumentation
Vergleichen Sie die Menge der URLs in der llms.txt mit Ihrer sitemap.xml oder dem Routen-Manifest Ihres Docs-Builds. Behandeln Sie jede URL als verdächtig, die in der llms.txt, aber nicht in der Sitemap auftaucht. Dasselbe gilt für Pakete. Jeder Name in der Datei sollte dem name-Feld einer von Ihnen veröffentlichten package.json entsprechen oder einer Abhängigkeit, die Sie bewusst empfehlen wollen.
Ein Node-Skript ohne Abhängigkeiten zur Validierung der llms.txt
Dieses Skript nimmt eine URL oder einen lokalen Pfad entgegen. Es prüft die H1 und jeden absoluten Link. Danach schlägt es jedes Paket, das in einem Installationsbefehl genannt wird, in der npm-Registry nach. Es nutzt das globale fetch von Node. Dadurch läuft es ohne jegliche Abhängigkeiten auf einer aktuellen LTS-Version wie Node 24.
// scripts/check-llms.mjs — usage: node scripts/check-llms.mjs <url-or-path>
import { readFile } from 'node:fs/promises';
const src = process.argv[2];
if (!src) { console.error('usage: node check-llms.mjs <url-or-path>'); process.exit(2); }
const failures = [];
async function load(s) {
if (!/^https?:\/\//.test(s)) return readFile(s, 'utf8');
const res = await fetch(s);
if (!res.ok) { console.error(`✗ ${s} returned ${res.status}`); process.exit(1); }
return res.text();
}
async function checkLink(url) {
let res;
try {
res = await fetch(url, { method: 'HEAD', redirect: 'follow' });
} catch {
// some servers reset the connection on HEAD; fall through to GET
}
try {
if (!res?.ok) res = await fetch(url, { redirect: 'follow' }); // some servers refuse HEAD
if (res.status >= 400) failures.push(`link ${res.status}: ${url}`);
else if (res.redirected) console.warn(`↪ ${url} -> ${res.url}`);
} catch (err) {
failures.push(`link unreachable (${err.cause?.code ?? err.message}): ${url}`);
}
}
async function checkPackage(spec) {
const [, name, version] = spec.match(/^(@?[^@\s]+)(?:@(\S+))?$/) ?? [];
if (!name) return;
const res = await fetch(`https://registry.npmjs.org/${name.replace('/', '%2F')}`);
if (res.status === 404) return failures.push(`npm: "${name}" is not registered`);
if (!res.ok) return failures.push(`npm: registry returned ${res.status} for "${name}"`);
const doc = await res.json();
if (/^\d+\.\d+\.\d+/.test(version ?? '') && !doc.versions?.[version])
failures.push(`npm: ${name}@${version} does not exist`);
}
const text = await load(src);
if (!/^# \S/.test(text.trimStart())) failures.push('structure: file does not start with an H1');
const links = new Set([...text.matchAll(/\]\((https?:\/\/[^)\s]+)\)/g)].map((m) => m[1]));
const specs = new Set([...text.matchAll(/\b(npx|npm (?:install|i|add)|pnpm add|yarn add)\s+([^\n`]+)/g)]
.flatMap(([, cmd, args]) => {
const tokens = args.trim().split(/\s+/).filter((t) => !t.startsWith('-'));
return cmd === 'npx' ? tokens.slice(0, 1) : tokens;
}));
for (const url of links) await checkLink(url);
for (const spec of specs) await checkPackage(spec);
failures.forEach((f) => console.error(`✗ ${f}`));
console.log(`${links.size} links, ${specs.size} package refs, ${failures.length} failures`);
process.exit(failures.length ? 1 : 0);
So funktioniert es:
loadliest die Datei von der Festplatte oder per HTTP. Schlägt bereits der Abruf der llms.txt-URL selbst fehl, bricht das Skript sofort ab.checkLinkversucht zunächstHEAD. Wirft derHEAD-Request einen Fehler oder liefert er einen Nicht-OK-Status, wiederholt die Funktion die Anfrage mitGET. Weiterleitungen werden als Warnungen protokolliert. Fehler auf Netzwerkebene, etwa eine nicht auflösbare Domain, werden als Fehlschläge erfasst.checkPackageschlägt jeden Namen in der npm-Registry nach. Die Funktion markiert nicht existierende Namen sowie exakte Versionen, die nie veröffentlicht wurden.- Die Requests laufen bewusst nacheinander, um die Last auf Ihren Servern und der Registry gering zu halten.
- Jeder Fehlschlag führt dazu, dass das Skript mit Exit-Code 1 beendet wird.
Das Skript hat bekannte Einschränkungen. Relative Links werden übersprungen. Fließtext, der in derselben Zeile auf einen Installationsbefehl folgt, kann False Positives erzeugen. Versionsbereiche werden nicht geprüft, sondern nur exakte Versionen.
Ein fehlgeschlagener Lauf sieht etwa so aus (beispielhafte Ausgabe mit erfundenen Namen):
↪ https://docs.example.com/guide -> https://docs.example.com/
✗ link unreachable (ENOTFOUND): https://docs.old.example.com/api
✗ npm: "example-sdk-legacy" is not registered
42 links, 3 package refs, 2 failures
Die Prüfung in der CI bei jedem Deploy und wöchentlich ausführen
Führen Sie den llms.txt-Validator auf zwei Wegen aus. Lassen Sie ihn einmal bei jedem Deploy gegen die gerade gebaute Datei laufen und einmal nach Zeitplan gegen die Live-Datei. Der Deploy-Lauf findet Fehler, die Sie selbst einführen. Der geplante Lauf erkennt Link Rot und upstream zurückgezogene Pakete, auch wenn Ihr Repo unverändert bleibt.
name: Validate llms.txt
on:
push:
branches: [main]
schedule:
- cron: '0 6 * * 1' # Mondays 06:00: catches link rot and unpublished packages
workflow_dispatch:
jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/setup-node@v7
with:
node-version: 24
- run: node scripts/check-llms.mjs https://docs.example.com/llms.txt
Der schedule-Trigger erwartet einen Standard-Cron-String mit fünf Feldern. GitHub interpretiert ihn als UTC, sofern Sie keinen timezone-Schlüssel mit einem IANA-Zonennamen hinzufügen. Mit workflow_dispatch können Sie einen Lauf manuell auslösen. Liegt Ihr Docs-Deployment im selben Workflow, fügen Sie vor dem Publish-Schritt einen Schritt hinzu, der auf die Build-Ausgabe zeigt, zum Beispiel node scripts/check-llms.mjs dist/llms.txt. So blockiert eine fehlschlagende Prüfung das Release. Lassen Sie den geplanten Job weiterhin auf die Live-URL zeigen.
llms.txt durch Generierung synchron halten
Wenn Sie die llms.txt aus derselben Quelle wie Ihre Dokumentation generieren, kann die Datei keine Seite und kein Paket mehr auflisten, das in der Doku fehlt. Erzeugen Sie die Datei im selben Schritt, in dem die Dokumentation gerendert wird, und verwenden Sie dabei dieselbe Seitenliste und dasselbe Frontmatter:
// scripts/build-llms.mjs
import { writeFile } from 'node:fs/promises';
import pages from '../dist/pages.json' with { type: 'json' };
const lines = ['# Example SDK', '', '## Docs', ''];
for (const p of pages.filter((p) => !p.draft)) {
lines.push(`- [${p.title}](https://docs.example.com${p.path}): ${p.description}`);
}
await writeFile('dist/llms.txt', lines.join('\n') + '\n');
Bearbeiten Sie die generierte Datei niemals von Hand. Committen Sie entweder nur den Generator oder sowohl den Generator als auch seine Ausgabe. Im zweiten Fall sollte die CI fehlschlagen, wenn eine erneute Generierung einen Diff erzeugt. Sobald die Generierung eingerichtet ist, dient der Validator als Absicherung für den Generator. Er ist dann kein Aufräumjob mehr, an den jemand denken muss.
Fazit
Eine llms.txt-Datei hilft nur, wenn alles, was sie auflistet, noch existiert und seiner Beschreibung entspricht. Agents handeln nämlich danach, ohne nachzuprüfen. Fügen Sie scripts/check-llms.mjs zu Ihrem Repo hinzu und führen Sie das Skript einmal gegen Ihre Live-Datei aus. Beheben Sie alles, was es meldet, und committen Sie anschließend den Workflow. Dann lässt der nächste defekte Link oder verwaiste Paketname einen Build fehlschlagen, bevor ein Agent darauf stößt.
FAQs
Sollte llms-full.txt genauso validiert werden wie llms.txt?
Ja, was Links und Pakete betrifft. Bei Paketen sollten Sie sogar noch strenger prüfen. llms-full.txt bettet den vollständigen Inhalt der Dokumentation ein. Daher enthält sie meist weit mehr Installationsbefehle und Codebeispiele als llms.txt, und jeder davon nennt einen Paketnamen, den ein Agent ausführen könnte. Die Strukturprüfungen können lockerer ausfallen, da die Datei überwiegend aus Seiteninhalten besteht und nicht aus einer kurzen Linkliste. Richten Sie dasselbe Skript auf beide Dateien.
Warum meldet die Linkprüfung 403-Fehler für Seiten, die im Browser problemlos laden?
Vermutlich blockiert ein Bot-Schutz oder eine Web Application Firewall die automatisierte Anfrage. Manche CDNs und Sicherheitsschichten lehnen Anfragen ab, denen browserähnliche Header fehlen oder die aus IP-Bereichen von CI-Runnern stammen. Setzen Sie Ihren CI-Runner oder den User-Agent des Checkers für Ihre eigene Docs-Domain auf die Allowlist. Alternativ senden Sie mit fetch einen aussagekräftigen User-Agent-Header. Ignorieren Sie 403-Fehler nicht einfach, denn Agents werden möglicherweise auf dieselbe Weise blockiert.
Wie prüfe ich Python-Paketnamen aus der llms.txt gegen PyPI?
Fragen Sie die JSON-API von PyPI unter pypi.org/pypi/NAME/json ab. Für ein existierendes Projekt liefert sie Metadaten sowie ein releases-Objekt, dessen Schlüssel die Versionsstrings sind. So können Sie sowohl den Namen als auch eine gepinnte Version bestätigen. Gibt die Anfrage 404 zurück, behandeln Sie den Namen als nicht registriert. Extrahieren Sie Namen aus pip install- und uv add-Befehlen auf dieselbe Weise, wie das Skript npm install verarbeitet.
Bedeutet eine bestandene Registry-Prüfung, dass ein Paket aus der llms.txt sicher installiert werden kann?
Nein. Die Prüfung bestätigt, dass der Name existiert, aber nicht, dass er den richtigen Personen gehört. Möglicherweise hat jemand einen Namen übernommen, nachdem das ursprüngliche Paket aufgegeben wurde. Außerdem kann ein Tippfehler in der llms.txt auf ein zum Verwechseln ähnliches Paket verweisen, das tatsächlich existiert. Prüfen Sie bei jedem empfohlenen Paket, ob Publisher und Repository-Link zu Ihrer Organisation passen. Bevorzugen Sie außerdem Scoped Packages, die Sie selbst kontrollieren.