Colophon / The Bot Crawls 001

The Bot Crawls 001: what actually found a new website

In the three days around this site going live, Cloudflare's adaptive dataset returned an estimated 8,492 requests for the window. 585 carried user-agent strings associated with AI companies: 6.9% of the requests, and 31.0% of everything the server sent back. The Field Tests ask what the models say about me. This asks what their software actually did.

One word does a lot of work on this page. Cloudflare can verify a crawler's identity through bot detection IDs, and that field was not available in this workflow, so every figure here is matched on the user-agent STRING, which anyone can type. So the traffic below is AI-labelled, never "verified AI", and the difference is not pedantry: the most interesting hour in the whole window is one where the label was probably spoofed.

The window

19 July to 21 July 2026, UTC, taken as three separate one-day queries because Cloudflare caps this dataset at a day per query. The site went live at 00:07Z on 19 July (01:07 BST), so the window opens on launch day and closes 23 hours into 21 July. Each query asked for a whole UTC day, half open from midnight to midnight, but the last one ran before the day's final hour had been aggregated, so the export stops after the 22:00Z bucket. The last hour is drawn hatched on every board below to mark a day that stops early, not an hour that was measured short.

19 July

5.2%

195 AI-labelled of 3,770 requests, 1.32 MB sent

20 July

6.1%

172 AI-labelled of 2,817 requests, 17.77 MB sent

21 July

11.4%

218 AI-labelled of 1,905 requests, 34.14 MB sent

The share climbs across the three days while the absolute traffic falls, which is the launch noise draining away and leaving the crawlers behind. It is the one trend in this study I would not yet call a finding: three days is three points.

The day was completed later, and the figures above did not move. On 22 July, after the UTC day had closed, the same queries were run again. 19 July and 20 July came back identical: the same request counts and the same byte totals, to the byte. 21 July came back with 23 hours that also matched exactly, plus one hour the first capture had never held, 23:00Z, carrying 49 requests. The completed window is 8,541 requests against the 8,492 published here.

Those 49 requests are kept as completion evidence rather than folded into the study. Every figure on this page stays tied to the nine originally banked responses, because a figure tied to a frozen file can be checked by anyone re-running the derivation, and a page that quietly adopts the newest capture drifts where nobody can see it. The larger number is not a better estimate of the same thing: it is a slightly longer window.

Cloudflare samples this dataset adaptively, so a re-query can move in general. That is a real limitation and it is stated below. It is not what moved this one: the hours present in both captures agreed exactly, and the whole difference is an hour that the first capture ran too early to see.

What is excluded, and why

20.8% of all traffic in the window was our own. It is removed from every AI figure above and named here rather than quietly dropped, because a study that trims its own footprint without saying so is not a study.

AgentRequestsWhy it is excluded
TLM-Audit-Scanner 1,384 Kish's own site audit scanner
CodexSiteAudit 236 OpenAI Codex's site audit, run for this project
curl 125 The desk checking headers and status codes from the command line
Plausible 24 The analytics service, not a crawler
Claude-User 1 A person driving a browser through Claude, not a crawler
Total excluded 1,770 20.8% of all traffic in the window

Who came

One row per operator, one bar per hour, across all 71 hours of the window. Each row is scaled to its own busiest hour, and that peak is printed at the end of the row, so the rows are comparable in SHAPE and never in height: a full bar on Diffbot is two requests and a full bar on Anthropic is dozens. Sharing one scale would flatten every small operator into a flat line, which would be tidier and would hide the finding.

AI-labelled requests by operator requests per hour, each row scaled to its own maximum, which is the figure at the end of the row
Anthropic 46
OpenAI 160
Amazon 17
Apple 16
Perplexity 14
Mistral 10
ByteDance 5
Cohere 4
Diffbot 2
Google 2
07-19 12 07-20 12 07-21 12
peak/hour
  1. 07-19 00:00Z The site goes live, 00:07Z (01:07 BST) on 19 July
  2. 07-19 07:00Z The credential-probe burst: 99 404s, thirteen identities, nine companies
  3. 07-20 10:00Z to 13:00Z Field Test 003's capture window, 11:45 to 14:00 BST, taken here as 10:45 to 13:00Z

Derived from the hourly-by-agent GraphQL cut for each of the three days, requestSource eyeball, grouped by operator from the user-agent string.

OperatorRequestsSent to them
Anthropic 276 18.33 MB
OpenAI 210 29.70 MB
Amazon 46 4.86 MB
Apple 16 0.10 MB
Perplexity 14 0.09 MB
Mistral 10 0.06 MB
ByteDance 5 0.03 MB
Cohere 4 0.03 MB
Diffbot 2 0.01 MB
Google 2 0.01 MB

Two operators account for most of it, and they behave differently: Anthropic's crawlers made the most requests, OpenAI's took the most bytes. The crawler-level cut says why, and it is the first thing on this page I did not expect.

Every AI-labelled crawler in the window, by name
Crawler, as its user agent gives itRequestsSent
ClaudeBot/1.0 239 18.23 MB
GPTBot/1.4 159 29.41 MB
Amazonbot/0.1 46 4.86 MB
Claude-SearchBot/1.0 37 0.10 MB
GPTBot/1.3 20 0.13 MB
OAI-SearchBot/1.0 15 0.08 MB
ChatGPT-User/1.0 11 0.08 MB
MistralAI-User/1.0 10 0.06 MB
PerplexityBot/1.0 8 0.05 MB
Applebot-Extended/0.1 8 0.05 MB
Applebot/0.1 8 0.05 MB
Perplexity-User/1.0 6 0.04 MB
OAI-SearchBot/1.4 5 0.01 MB
Bytespider 5 0.03 MB
cohere-ai 4 0.03 MB
Diffbot/1.0 2 0.01 MB
GoogleOther 2 0.01 MB

What the status codes say

StatusRequestsShare
200 446 76.2%
404 101 17.3%
302 34 5.8%
301 3 0.5%
499 1 0.2%

The 302s are the Cloudflare Access gate working: 34 redirects to a login page, every one of them a request for a private /lab/ route. The 404s are a different story: 99 of the 101 fall in a single hour on launch day, and the other 2 are ordinary crawlers meeting a missing page on the two days after.

One crawler, tested against the gate

A focused raw-event extraction for 20 July, run later and separately, shows what those redirects look like from the crawler's side. It returned 57 rows for /lab/ across 39 distinct paths. 42 rows carried status 302 and 15 carried status 200. Rows, not requests: a raw-event query returns sampled events and cannot measure volume, which is why no total on this page comes from one.

35 rows presented the Claude-SearchBot user agent, across 35 different /lab/ paths: one row each, a methodical sweep rather than a retry loop. 34 were redirected by Cloudflare Access. One returned 200, /lab/thesis/, which is the page deliberately excepted from the gate. All 35 came through one source address on the same Amazon.com, Inc. network, AS16509. The remaining 22 rows in the extract, including 14 of the 200s, are first party: me and the desk reading the benches from UK consumer networks, signed in through Access.

The source network, the one-request-per-path pattern and the /robots.txt behaviour are all consistent with documented Claude-SearchBot behaviour: the file allowed search crawlers into /lab/ at the time, and ClaudeBot, which the same file disallowed, never tried. They do not independently verify the crawler's identity. Amazon.com, Inc. is shared infrastructure that anyone can rent, and Anthropic states that it does not publish stable crawler IP ranges because its crawlers run on service-provider addresses. Consistent behaviour from a plausible network is the most this evidence supports. Note also what a 302 is: Access answered with a redirect to a login page, so the protected pages behind it were not served.

The probe hour

Every one of the 99 launch-day 404s falls inside a single hour, 07:00Z on 19 July, which is nearly seven hours after the site went live at 00:07Z. They are requests for credentials and configuration: /.env.backup, /.ssh/id_dsa, /.config/anthropic/credentials/default.json, /.codex/config.toml.

They arrive wearing 13 different AI crawler identities belonging to 9 companies: Amazon, Apple, ByteDance, Cohere, Diffbot, Google, Mistral, OpenAI, Perplexity. Requests for credential and private-key paths are inconsistent with the documented purposes of those crawlers, and strongly suggest spoofed user-agent labels.

A later raw-event query, narrowed to that hour, adds source-level evidence to the pattern. It returned 26 rows, every one a 404, across 24 paths and 19 user-agent strings from 4 sources. 23 of the 26 shared one source label. That source presented 17 distinct user-agent strings, thirteen AI crawler labels and four ordinary browser strings, and requested 23 paths in about 21 seconds.

That clustering is consistent with coordinated scanning, or with shared network egress. It does not identify one machine or one actor, and the record says why: the network behind that source is Cloudflare, Inc., AS13335, and a proxy or network egress of that kind aggregates many clients behind one address. The page claims a coordinated burst, not a culprit.

The response sizes corroborate rather than prove. Every probe the primary path cut measured was served the same 404 page, between 6,226 and 6,275 bytes apiece, which is what one 404 template looks like from any number of clients. Identical response sizes would look the same whether this was one scanner or several working from the same wordlist, so on their own they show a shared template, never a shared actor.

No Anthropic-labelled user agent appeared among the 99 credential-path probes. This is enforced by a build check: if a future change to the banked evidence or classifier introduces a Claude-labelled request into the burst, the study will fail to build until the discrepancy is reviewed. During the same 07:00Z hour, ClaudeBot made two successful requests consistent with its wider crawling pattern. The burst is therefore defined by behaviour, requests for credential and configuration paths returning 404, not merely by their timing.

All 99 probe requests, by asserted identity and path
Identity worn Path asked for Requests Path evidence source
GPTBot/1.3 /.boto 10 Primary banked path capture
GPTBot/1.3 /.codex/config.toml 10 Primary banked path capture
MistralAI-User/1.0 /.hermes/auth.json 10 22 July unrestricted replication
Applebot-Extended/0.1 /.env.staging 8 Primary banked path capture
Applebot/0.1 /localhost.key 8 22 July unrestricted replication
OAI-SearchBot/1.0 /config.env 8 Primary banked path capture
PerplexityBot/1.0 /.env.backup 8 Primary banked path capture
Amazonbot/0.1 /@fs/.env 6 Primary banked path capture
Perplexity-User/1.0 /.config/anthropic/credentials/default.json 6 Primary banked path capture
ChatGPT-User/1.0 /.env.prod.bak 4 Primary banked path capture
cohere-ai /values.yaml 4 Primary banked path capture
Bytespider /.ssh/id_dsa 3 Primary banked path capture
Bytespider /id_rsa 2 Primary banked path capture
ChatGPT-User/1.0 /.gitlab-ci.yml 2 Primary banked path capture
ChatGPT-User/1.0 /.github/workflows/deploy.yml 2 Primary banked path capture
Diffbot/1.0 /.ssh/known_hosts 2 Primary banked path capture
GoogleOther /rclone.conf 2 22 July unrestricted replication
OAI-SearchBot/1.0 /config.js 2 Primary banked path capture
OAI-SearchBot/1.0 /.env 2 Primary banked path capture

The identities, hour, statuses and counts of the 20 requests marked as replication-sourced were already established by the primary dataset. The later unrestricted replication supplied their previously missing paths, and nothing else.

A gap in the evidence, named, and since closed. The original path query was filtered to ten user-agent patterns, and three of the agents in this burst matched none of them, so 20 of the 99 probes had no path on record. Their identity, hour, status and count were never in doubt; only what they asked for was missing.

An unrestricted capture on 22 July closed it, inside Cloudflare's seven-day retention window. It returned the burst as the primary dataset already had it, the same 99 requests in the same hour under the same 13 identities, and added the three missing paths: MistralAI-User/1.0 asked for /.hermes/auth.json, 10 times; Applebot/0.1 asked for /localhost.key, 8 times; GoogleOther asked for /rclone.conf, 2 times. Marked in the table above, because a reader should be able to see which line of evidence each row rests on.

Corrections, kept where they happened. The plan said a scanner impersonated thirteen AI companies; it was thirteen identities across nine companies. The first build of this page then reported the burst as 79 requests across seven companies, because its classifier was copied from that same ten-pattern query and so could not see MistralAI-User, plain Applebot or GoogleOther: twenty requests, every one a 404 inside this hour. A sweep of all 179 unmatched agent strings confirms those three were the only ones missing. Found by OpenAI Codex reviewing the built page, which is the second desk doing what it is for.

What they took

With the probes set aside, the rest reads like ordinary crawling: robots.txt, the sitemap, real pages, the Markdown companions, the PDFs.

Most requested

/robots.txt 48
/sitemap-index.xml 42
/ 6
/_astro/id-medical-school.CBEj4qaS_1u2o52.webp 6
/speaking/2026-ugc-panel/ 5
/ideas/referrer-society/ 5
/work/broadcast/cyber-2/ 5
/docs/broadcast-nhs-111.md 5
/docs/field-test-2026-07-07-anthropic-claude-opus-4-8.md 5
/docs/field-test-2026-07-17-anthropic-claude-haiku-4-5-thinking-off.md 5
/work/broadcast/ 4
/_astro/2023-glocalising-modular-content.bJnpDEox_1mlVeV.webp 4

Heaviest

/headshots/dr-kishan-rees-portrait-colour.jpg 15.24 MB
/docs/referrer-society.pdf 10.17 MB
/docs/pbl-sgt-fusion.pdf 8.56 MB
/docs/medsin.pdf 3.21 MB
/_astro/reuters-stage.Cu9J0aTU_lwOgG.webp 1.70 MB
/_astro/id-medical-school.CBEj4qaS_1u2o52.webp 0.93 MB
/_astro/2024-ai-debate.Br3VN9zM_Z2mwP4o.webp 0.88 MB
/_astro/team-sanofi.9Sf5lSXg_ZqWxNq.webp 0.84 MB

The top four files are 70.7% of the response bytes sent to AI-labelled traffic once the probe burst is set aside, which is the denominator that matters for crawler cost. The press headshot alone accounts for 15.24 MB across 3 requests, which is the measured cost of one image of 5.08 MB (4.84 MiB) that was never resized. Every byte figure on this page is decimal MB, a million bytes, which is the unit the raw sums are in; the binary equivalent is given here once because the two conventions differ by five per cent and mixing them is how a file grows or shrinks in the retelling.

Limits, stated rather than buried

  • A user agent is a claim, not an identity. Without botDetectionIds nothing here is verified. Every total is AI-labelled.
  • Crawling is not citing. This page says what was fetched. Whether any of it reached an answer is the Field Tests' question, and on 20 July their answer was that none of the seven sessions cited this site.
  • The window stops an hour short of the third day. The primary capture ends after the 22:00Z bucket on 21 July, and that last hour is hatched on every board. A completion capture the next day showed the hour it missed held 49 requests, 1 of them AI-labelled.
  • Three days is three days. The rising AI share is a shape, not a trend, and the window is short enough that one scanner moved it.
  • requestSource is eyeball, which restricts the query to Cloudflare's end-user request-source class. That is not a raw log, it does not verify a crawler's identity, and it is not a guarantee that every Cloudflare-branded service user agent is absent from these totals.
  • Every count and byte sum on this page is an estimate, not a tally. httpRequestsAdaptiveGroups is an adaptive dataset: Cloudflare samples it and returns estimates, and it publishes confidence intervals for quantifying the uncertainty. Freezing the response files makes those API outputs reproducible; it does not turn them into exact origin-log counts, and the two completed days replicating byte for byte shows the API answering consistently rather than the numbers being exact.
  • What the intervals do and do not cover. The original nine queries did not capture confidence intervals, so the figures published here cannot be given one after the fact. The later control queries did capture them, but for overall request counts only, not AI-labelled subsets or byte sums: nothing on this page about operators, paths, percentages or the AI-labelled subset has an interval behind it. Where the later window matches the frozen one, the intervals were 19 July, 3,378 to 4,162 around 3,770; 20 July, 2,707 to 2,927 around 2,817. The 21 July control is not comparable and is not quoted: it covers the completed 24-hour day around 1,954, while the figure frozen here is 1,905 over 23 hours, so the interval belongs to a different quantity. Read the two that are quoted as the order of the sampling uncertainty, not as a bound on every number here. The confidence level is not stated because it is not on record: Cloudflare returns the estimate, the bounds and the sample size with no level field, and the query text was not banked beside the response. Study 002 banks the query with the response, which is the only thing that makes it knowable.
  • The raw-event queries answer a narrower question than the grouped ones. sampleInterval is not available as a field on the individual httpRequestsAdaptive events used here, and rayName was not authorised for this zone and token. Those rows are good for attribution and behaviour, and they can never stand in for the grouped totals, which are where the adaptive counts, the sums and the confidence bounds live. Nothing on this page counts volume from a raw-event query.

Evidence

Fifteen files sit behind this page and they are not one kind of thing. Naming them separately is the point: the moment replication, completion and validation evidence are allowed to blur into the primary dataset, no figure can be traced to the file it came from.

ClassWhat it isWhat it is used for
Primary Nine banked GraphQL responses, three cuts for each day in the window Every headline figure and the whole main derivation
Same-window replication Grouped captures of 19 July and 20 July, both completed UTC days Checking the primary totals reproduce, and closing the probe path gap
Completion A grouped capture of 21 July taken after the UTC day closed Measuring the hour the primary capture ran too early to see
New partial day A grouped capture of 22 July, 19 hours to 18:00Z Provenance for study 002. It is a part day and is never treated as a whole one
Focused validation Two raw-event queries: the probe hour, and the /lab/ requests of 20 July Behaviour and attribution only, never a total

The primary nine are banked at source-materials/crawler-telemetry/ and the later six beside them in replication-2026-07-22/, all recomputed by site/scripts/derive-bot-crawls.mjs. The queries were restricted to a single Cloudflare zone using its private zone identifier, retained in the archived research record but omitted here. Query design, extraction and the first analysis: OpenAI Codex (GPT-5.6 Sol) with Kish, 21 to 22 July 2026. Recomputed from the raw responses by this script.

Those paths are inside a private repository, so they are a provenance record rather than something a reader can open. The banked files are privately archived and available on request, and any figure here can be checked against them by re-running the derivation script on the frozen responses.

The two raw-event files carry client IP addresses and are not in the repository. The originals stay private. What is banked is a derivative with the address removed and replaced by a study-local label (source-A and so on), which keeps the only thing the analysis needs, that certain events shared one source, and publishes no address. A plain hash was considered and rejected: an unkeyed hash of an IPv4 address is not anonymisation, because the whole space can be enumerated in minutes. Network and country are kept, since they carry the reading without carrying the address.

Six reconciliation gates pass on the primary figures at derivation: per-day totals sum to the whole, operator and crawler totals sum to the AI total, the status cut agrees with the hourly cut, and every excluded agent is accounted for in the table above. A further fifteen gates check the later evidence, including that the two completed days replicate exactly, that every hour present in both 21 July captures agrees, that the replication only ADDED paths and never changed one, and that no client address survives into the banked derivatives.

How this was made

Three parties, and the division of labour is worth stating because it is the argument this site keeps making about working with these tools.

  • I set the question, ran and banked the original Cloudflare extraction by hand, took the interpretive and ethical decisions, and hold final editorial authority over every word of it.
  • OpenAI Codex designed the GraphQL query approach and the first analysis, later ran the controlled recapture and the focused raw-event queries, reconciled the evidence, designed the extraction protocol, and did the privacy and acceptance review.
  • Claude Code never touched Cloudflare. It implemented and tested the study from the evidence supplied and the brief agreed, and wrote the derivation that recomputes every figure here.

What that arrangement bought was not agreement. Two systems agreeing proves nothing, and where they disagreed the disagreement had to be settled against the data, which is how the classifier error and the sampling misreading were both caught. What it bought was procedure: evidence banked before it was interpreted, assumptions challenged by someone who had not formed them, different classes of error found by different readers, implementation kept separate from evidence review, and a person accountable at the end.

Cite this study

Rees, K. The Bot Crawls 001: what actually found a new website. drkishanrees.com. Data captured 19 to 21 July 2026; compiled 23 July 2026. https://drkishanrees.com/colophon/bot-crawls-001/

First published . Last amended .

Something sparked your interest? Let's talk!