feat(seo): open the crawler policy to every operator - #10474
Conversation
Replaces the retrieval-yes / training-no split from #9633 with a single open policy: retrieval, citation, indexing and training are permitted for everyone. The split was never coherent here. The catalogue is MIT-licensed and published to be reused — every page ships its runnable source, /llms.txt describes the site for agents, an MCP server exists so assistants can query it directly. Declining a training crawler protected nothing the licence had not already granted, and cost reach. It also had a concrete consequence nobody intended. Google offers one token, Google-Extended, governing Gemini grounding AND training together; no finer control exists. Declining it as though it were training-only kept anyplot out of Gemini's answers entirely, which is the opposite of what a catalogue built for AI consumption wants. Applebot-Extended really is training-only, so declining it was correct before and is simply no longer wanted. The Content-Signal line moves to ai-train=yes and the Article 4 reservation is removed rather than left standing in contradiction to the licence three lines above it. A reservation that contradicts your own licence is worse than none. Bytespider stays declined, on bandwidth rather than principle: documented as ignoring robots.txt and crawling far harder than this catalogue can justify serving. The group states intent; Cloudflare does the enforcing. With the policy uniform, the named allow-groups became redundant — they resolve identically to `User-agent: *` — so the file collapses to one declined group and the wildcard, removing the ordering traps its own header warned about. Verified with urllib.robotparser across eleven agent/path pairs, including that /debug and /interactive stay declined. This does NOT take effect until the Cloudflare dashboard is updated: the edge still 403s GPTBot, CCBot, Amazonbot, meta-externalagent and Google-CloudVertexBot. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
There was a problem hiding this comment.
Pull request overview
This PR updates anyplot’s crawler/robots policy documentation and robots.txt to permit retrieval, citation, search indexing, and model training for all operators (with a bandwidth-based exception for Bytespider), aligning the repo-side policy with the intended licensing stance and simplifying the robots group structure.
Changes:
- Simplifies
app/public/robots.txtto a single declined group (Bytespider) plus the wildcard group withContent-Signal ... ai-train=yes. - Updates
docs/reference/seo.mdto record the new “everything open” decision and the Cloudflare edge/dashboard enforcement caveat. - Adds a detailed
[Unreleased]changelog entry describing the policy change and the required Cloudflare dashboard alignment.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| docs/reference/seo.md | Updates the documented AI crawler policy decision and Cloudflare enforcement notes. |
| CHANGELOG.md | Adds an [Unreleased] entry describing the updated crawler policy and operational requirement. |
| app/public/robots.txt | Implements the simplified “open by default” robots policy with a Bytespider exception and updated content signals. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
… PR refs Both from the Copilot review. The "Robots.txt" section still described the four-group structure — named allow group, declined training collectors, opt-out tokens, wildcard — and quoted a verbatim block with `ai-train=no`, three screens above the section declaring the policy open. A document contradicting itself is worse than either version alone. It now describes the two groups that exist and quotes the declined one. The backend subsection also still showed the old blanket `Disallow: /`, from before /og/ was opened. The changelog point turned out to be broader than the one bullet it was raised against: this file's convention is to close an entry with its PR reference, and twelve entries from this run of SEO work had none — #10452, #10453, #10455, #10458, #10462, #10465, #10470 through #10474. The already-merged ones are fixed here too, since they all live in the same file and a half-referenced changelog is the harder thing to audit at release time. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Both applied, and the second was broader than the line it was raised against. The self-contradiction was real and worse than flagged. The "Robots.txt" section still described four groups and quoted a verbatim block containing The changelog convention was missing from twelve entries, not one. Checking the file properly showed this whole run of SEO work shipped without PR references: #10452, #10453, #10455, #10458, #10462, #10465, #10470–#10474. The merged ones are fixed here too — they live in the same file, and a half-referenced changelog is the harder thing to audit at release time than a fully un-referenced one. |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 3 out of 3 changed files in this pull request and generated no new comments.
Suppressed comments (5)
docs/reference/seo.md:336
- This sentence says "Allow comes first" but doesn’t explain that it’s because
/og/is the specific exception and/is the broad rule. Making that explicit keeps it consistent with the ordering rationale described above.
`/og/` is the exception: every prerendered page references its preview image
there, so a blanket `Disallow` pointed crawlers at an image they were forbidden
to fetch. `Allow` comes first for the first-match parsers described above.
docs/reference/seo.md:322
- The wording here implies a hard rule that
Disallow:must always come beforeAllow:. A few lines later (API robots.txt) the file relies on the opposite ordering for a more-specific allow exception. Rephrase this bullet to describe the underlying rule (most-specific match first) to avoid contradictory guidance in the same document.
This issue also appears on line 334 of the same file.
- Inside each group, `Disallow:` comes **before** `Allow: /` — same reason: with
the broad allow first, a first-match parser (Python's `urllib.robotparser`,
for one) hands out `/debug` and `/interactive`.
app/public/robots.txt:13
- The repository’s documentation consistently uses US English (“license”). This file introduces the British spelling “licence”, which is inconsistent with the surrounding docs and style guidance.
# So this file grants what the licence already grants. Retrieval, citation,
# search indexing and model training are all permitted, for every operator,
# without the retrieval-yes / training-no split this file used to draw. That
# split was never coherent here: the licence permits reuse including training,
# so declining a training crawler protected nothing while costing reach.
docs/reference/seo.md:352
- This section uses “licence”, but the rest of the docs use “license”. For consistency (and Google style), switch to US spelling.
licence had not already given away, while costing reach.
CHANGELOG.md:147
- This changelog entry uses the British spelling “licence”, but other docs in the repo use “license”. Align spelling for consistency.
declining a training crawler protected nothing the licence had not already granted, while costing
reach. It also had an unintended consequence — Google's `Google-Extended` token governs Gemini
**grounding and training together**, with no finer control, so declining it kept anyplot out of
Gemini's answers entirely. The `Content-Signal` line moves to `ai-train=yes` and the Article 4
reservation is gone rather than left contradicting the licence beside it. `Bytespider` remains
Same convention fix as #10474, applied to this entry. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Three findings from a fresh-context audit of today's twelve PRs. I had spotted none of them. ## 1. The documentation contradicted itself `docs/reference/seo.md`'s bot-map section still said `gptbot`, `meta-externalagent` and `amazonbot` were *"declined in robots.txt"*, and `app/nginx.conf` repeated the claim in a comment. Both were written about an hour before #10474 opened the policy, and neither was reconciled — so the page asserted the old policy three screens from the section declaring the new one. The measured edge-state table was stale the other way round: the dashboard unblock it prescribed had since been carried out, so the table described a state that no longer existed. It now records what is actually blocked (`Bytespider`, `TikTok Spider`, and three agents whose rule-compliance is unverified rather than disproven) and says plainly that `bot-serving-check` tests the origin and will never catch edge drift. ## 2. A database outage would reopen #10453 With no catalogue to check against, the bot routes answered `200` with a fabricated, self-canonicalising page for **any** string — the precise defect #10453 removed, surviving in degraded mode. Degraded pages now carry `noindex`. I first tried returning `503`, which is arguably more correct, and backed it out: it broke eleven tests that use the no-DB path as a rendering harness. `noindex` keeps the behaviour those tests depend on and removes the indexing risk, which is the part that matters. The path is unreachable in production — but "unreachable" here means one misconfiguration away from indexable. ## 3. 404s were counted as successful page reads `bot_fetch` ran as a router dependency. A dependency executes **before** the handler and cannot see the response, so every miss was recorded as a read. It has moved to a middleware and gained a `status` property. Recording the miss is right — an assistant asking for a URL that no longer exists is how a library migration announces itself — but recording it as a page view is a lie. Filter on `status` before reading anything else; documented in `docs/reference/plausible.md`. ## Also A docstring pointed at `app/src/router.tsx`, which does not exist. Routing lives in `app/src/routes/index.tsx`. ## Verification - `pytest tests/unit` — 1640 passed, including: degraded hub and impl pages assert `noindex`, a companion test asserts normal pages do **not**, and a `bot_fetch` test pins `status: "404"` on a miss - `ruff check` + `ruff format --check` — clean - `grep` confirms no remaining reference to the superseded policy in `seo.md`, `nginx.conf` or `robots.txt` ## Not in this PR The audit's other findings are handled elsewhere: the ten-day-red monitor in #10478, and the analytics that recorded nothing in #10477. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
The change
Retrieval, citation, search indexing and model training are now permitted for every operator, replacing the retrieval-yes / training-no split from #9633.
Google-ExtendedApplebot-ExtendedClaudeBotBytespiderContent-Signalai-train=no+ Art. 4 reservationai-train=yes, reservation removedWhy
The split was never coherent here. The catalogue is MIT-licensed and published to be reused — every page ships its runnable source,
/llms.txtdescribes the site for agents, an MCP server exists so assistants can query it directly. Declining a training crawler protected nothing the licence had not already granted, and cost reach.It also had a consequence nobody intended.
Google-Extendedis a single token governing Gemini grounding and training together; no finer control exists, verified against Google's crawler documentation. Declining it as though it were training-only kept anyplot out of Gemini's answers entirely.Applebot-Extendedreally is training-only (retrieval rides onApplebot), so declining that was correct before and is simply no longer wanted.It corrected an asymmetry that favoured one vendor.
ClaudeBot— Anthropic's training crawler, per Anthropic's own documentation — sat in the allow group while its exact counterpartGPTBotwas declined. Nothing recorded why. Opening both resolves it in the direction the licence already points.The Article 4 reservation
The old file declared
Content-Signal: ai-train=noas an express reservation of rights under Article 4 of EU Directive 2019/790. It is removed rather than left standing three lines from a licence that grants exactly what it reserves — a reservation contradicting your own licence is worse than none.Structural simplification
With the policy uniform, the named allow-groups became redundant: they resolve identically to
User-agent: *. The file collapses to one declined group plus the wildcard, which removes the first-match ordering traps its own header spent twenty lines warning about.Verified with Python's
urllib.robotparser— a strict first-match parser — across eleven agent/path pairs:Cloudflare AI Crawl Control still 403s
GPTBot,CCBot,Amazonbot,meta-externalagentandGoogle-CloudVertexBotat the edge, verified on the live zone 2026-08-18. A permission granted here that the edge blocks is a published lie. Unblock everything exceptBytespiderin the zone dashboard;docs/reference/seo.mdrecords the measured state and the verification commands.Also recorded there:
Google-CloudVertexBotis blocked at the edge and appears in no repo-side policy — a decision that exists only as a dashboard toggle. And Cloudflare is no longer prepending a managed robots.txt block, unlike the 2026-07-25 measurement; the live file is byte-identical to this one.🤖 Generated with Claude Code