Methods and limits
Every number on this site comes from one Caddy access log and one file of dated events. This page states how each figure is made, and where the method stops being trustworthy.
The classification cascade
pipeline/classify.sql assigns one class per request. It tests the user agent against an ordered list of rules and stops at the first match. Order is the method, not a detail. Named actors resolve before generic patterns, so a crawler that calls itself ClaudeBot lands in ai-anthropic even when its user agent also carries a Chrome version. Every page reads the same two DuckDB views, so a class cannot mean two things in two charts.
Version strings do not identify an actor. AionBot wears Chrome/126.0.0.0, Reflectionbot wears Chrome/151.0.0.0, and Amazonbot wears Chrome/119.0.6045.214. The rotator uses the reduced form that real Chrome also emits. Only the behaviour separates them.
Outcomes
aborted means HTTP status 0, the value Caddy writes when abort closes a connection. It is the only signal available for a request the server refused. A request counted as served returned 200.
Change detector
Each class becomes a daily series, zero-filled across the window, so a class that stops still registers. At each day the detector compares the mean of the three days before with the mean of the three days after. It adds 1 to both sides to survive zeros and flags a ratio at or above 2 or at or below 0.5. A window needs at least 20 requests to qualify. Consecutive flags of the same class and direction collapse into one episode.
This is a detector, not an estimator. Nineteen days cannot support an intervention analysis, and a flagged day is not proof that a rule caused the change. The dated events in src/data/events.csv name the rule changes. The detector names the departures.
Behaviour fingerprints
Requests per address, the share of addresses seen once, and the share of addresses seen on three or more days. These columns separate a fleet from a reader. A named crawler hides in a handful of addresses with thousands of requests each. The rotator appears as tens of thousands of addresses at about one request each.
Read the browser class with those columns in view. A real reader returns. A fleet does not.
What the site cannot tell you
- The rotator has no owner. The label describes behaviour, not identity. The BGP table names the address space it rents. It does not name who pays for it.
- No client rendered a page. The window holds no request for a stylesheet, a font, an icon, or a script that the cgit interface needs. Requests whose path ends in
.pngor.jsare files inside repositories, fetched through cgit'splain,tree, anddiffviews. - Consumer addresses stay out of the blocks. Part of the rotator sits in cable ranges. Every mitigation leaves that part in place, which is why a residual remains.
- The address map is a snapshot.
pipeline/asn-map.raw.txtcomes from one Team Cymru bulk whois run on 2026-09-26 and covers the addresses in that day's log. It is committed, not rebuilt on every refresh. - Fediverse traffic is undercounted. The log covers
/git/*, and link unfurls mostly land on the blog. That question needs therud.is/b/*log. - Denominators move. A share can fall while the absolute count rises, when a new actor arrives. The rules page shows both.
Not measured
- Address churn. Whether the rotator rents fresh addresses or cycles a fixed set is unknown from this window. First-seen and last-seen per address would answer it.
- Rule coverage after deployment. The report reads outcomes from the log. It does not read the live Caddyfile, so "rule present" and "rule effective" are compared by hand.
- Rate-limit effect. The
git_burstzone bounds bursts at 60 requests per hour per /24. What it would have caught is not simulated here.