<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: roadleon</title>
    <description>The latest articles on DEV Community by roadleon (@roadleon).</description>
    <link>https://dev.to/roadleon</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4106990%2Fafd39288-5c18-46eb-86a6-a43296c67f15.png</url>
      <title>DEV Community: roadleon</title>
      <link>https://dev.to/roadleon</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/roadleon"/>
    <language>en</language>
    <item>
      <title>Thirty-Four Out of Thirty-Four. Then Zero Out of Ten.</title>
      <dc:creator>roadleon</dc:creator>
      <pubDate>Fri, 25 Sep 2026 11:05:22 +0000</pubDate>
      <link>https://dev.to/roadleon/thirty-four-out-of-thirty-four-then-zero-out-of-ten-bf7</link>
      <guid>https://dev.to/roadleon/thirty-four-out-of-thirty-four-then-zero-out-of-ten-bf7</guid>
      <description>&lt;p&gt;My last article ended by admitting something my own dashboard could not do. Four days before it went out, I had already fixed it — and the fix had an answer waiting.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://ailys.app/en/blog/asked-for-my-env-file" rel="noopener noreferrer"&gt;The article&lt;/a&gt; was about twenty requests for an &lt;code&gt;.env&lt;/code&gt; file that does not exist on my site. Every one of them said it was ClaudeBot. I could not tell you whether they really were, because my dashboard verified crawlers, not paths: it would report that ClaudeBot as a whole was 68% verified that week, and stop there. Which of its requests were in the verified 68% and which were not, it had no way to say.&lt;/p&gt;

&lt;p&gt;That is now a column. Here is what it says.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two words that had been collapsed into one
&lt;/h2&gt;

&lt;p&gt;The fix is smaller than it sounds, and it is really two fixes. The first one I owe to a reader. When I published &lt;a href="https://ailys.app/en/blog/984-requests-said-perplexity" rel="noopener noreferrer"&gt;an article in early September&lt;/a&gt; about crawler verification, a commenter pointed out that my "unverified" number was doing two jobs at once: it counted requests that had been checked against the vendor's published IP ranges and failed, and it counted requests that had never been checked at all. A crawler with no published ranges and a crawler caught wearing a false name both came out as 0% verified. That is not a measurement. That is two different facts wearing the same number.&lt;/p&gt;

&lt;p&gt;So the plugin now records whether a check actually ran, separately from whether it passed. The second fix follows from the first: once you know which individual requests were checked, you can total them by path instead of by crawler. The page list grew a column reading &lt;code&gt;verified / checked&lt;/code&gt;, and paths where nothing has been checked yet say so in words rather than showing a zero.&lt;/p&gt;

&lt;h2&gt;
  
  
  The list, with the new column
&lt;/h2&gt;

&lt;p&gt;This is the top ten pages for the seven days ending September 22, in the order the dashboard shows them.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Path (requests)&lt;/th&gt;
&lt;th&gt;Verified / checked&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;/&lt;/code&gt; (40)&lt;/td&gt;
&lt;td&gt;22 / 22&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;/tag/news/&lt;/code&gt; (21)&lt;/td&gt;
&lt;td&gt;1 / 1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;/.env&lt;/code&gt; (14)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0 / 6&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;/topics/ダバオの運転事情/&lt;/code&gt; (9)&lt;/td&gt;
&lt;td&gt;6 / 6&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;/read-document&lt;/code&gt; (7)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0 / 3&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;/tag/知育教室/&lt;/code&gt; (7)&lt;/td&gt;
&lt;td&gt;2 / 2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;/api/designer/v1/file-content&lt;/code&gt; (6)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0 / 1&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;/attachment_tag/favicon/&lt;/code&gt; (6)&lt;/td&gt;
&lt;td&gt;not checked&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;/tag/lego/&lt;/code&gt; (6)&lt;/td&gt;
&lt;td&gt;2 / 2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;/tag/イスラム/&lt;/code&gt; (6)&lt;/td&gt;
&lt;td&gt;1 / 1&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Read down the right-hand column and the table sorts itself into two piles. Most of it is a small Japanese site about living in Davao being read the way you would expect: the front page, some tag listings, an article about driving there. Three rows are not like the others, because three of these paths do not exist and never have. &lt;code&gt;/.env&lt;/code&gt; is where a web application keeps its database passwords and API keys. &lt;code&gt;/read-document&lt;/code&gt; and &lt;code&gt;/api/designer/v1/file-content&lt;/code&gt; are endpoints belonging to software this site has never run. All three return 404.&lt;/p&gt;

&lt;p&gt;Now add up each pile.&lt;/p&gt;

&lt;blockquote&gt;
&lt;h3&gt;
  
  
  34 / 34
&lt;/h3&gt;

&lt;p&gt;Checks on the real pages. Every one of them matched the vendor it claimed to be.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Six paths had been checked at least once. The seventh, &lt;code&gt;/attachment_tag/favicon/&lt;/code&gt;, had not been checked at all, so it is in neither pile.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And the other pile.&lt;/p&gt;

&lt;blockquote&gt;
&lt;h3&gt;
  
  
  0 / 10
&lt;/h3&gt;

&lt;p&gt;Checks on the three paths that do not exist. Not one matched.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;All three name ClaudeBot as their most frequent visitor.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Same site. Same week. Same instrument, running the same comparison against the same published list of addresses. The only thing that changes between the two piles is whether the thing being asked for is really there.&lt;/p&gt;

&lt;h2&gt;
  
  
  This is not an argument about ClaudeBot
&lt;/h2&gt;

&lt;p&gt;It would be easy to read that as an accusation, so let me put the rest of the week's numbers next to it. Over those same seven days ClaudeBot made 599 requests to this site. 94% of them verified as Anthropic. 3% had not been checked yet. Which means that of everything I was able to check, almost all of it was exactly what it said it was.&lt;/p&gt;

&lt;p&gt;Anthropic began publishing its IP ranges on August 18, and on the evidence of this site that list works. The overwhelming majority of traffic calling itself ClaudeBot here is Anthropic. That is the point. A name that is usually honest is a much better disguise than one that is usually suspect, and the ten failures are not distributed evenly across the site's traffic — they are sitting on the three paths where somebody would go looking for credentials.&lt;/p&gt;

&lt;p&gt;One more detail, small but hard to unsee. Last week &lt;code&gt;/read-document&lt;/code&gt; was in this same table, and the name on it was PerplexityBot. This week the same non-existent path is being requested under ClaudeBot. The path did not change. The costume did.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the new column cannot reach
&lt;/h2&gt;

&lt;p&gt;Splitting "checked and failed" from "never checked" had a second effect I did not plan for: it made visible how much of this traffic never reaches the gate at all. Here is the whole week, sorted by what the instrument was able to do with each request.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Requests&lt;/th&gt;
&lt;th&gt;Share&lt;/th&gt;
&lt;th&gt;What the instrument could do&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1,809&lt;/td&gt;
&lt;td&gt;65%&lt;/td&gt;
&lt;td&gt;No verification method published by the vendor&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;~800&lt;/td&gt;
&lt;td&gt;29%&lt;/td&gt;
&lt;td&gt;Checked, matched the vendor it claimed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;~85&lt;/td&gt;
&lt;td&gt;3%&lt;/td&gt;
&lt;td&gt;Checked, did not match&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;~70&lt;/td&gt;
&lt;td&gt;3%&lt;/td&gt;
&lt;td&gt;Not checked yet&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;AI crawler requests to davaonline.net, seven days ending 2026-09-22. The first row is exact: Meta's two crawlers, Bytespider and Amazonbot publish neither IP ranges nor reverse DNS, so no check is possible. The other three are derived from the dashboard's rounded percentages and are accurate to within a few requests.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Two thirds of it cannot be checked by anyone, because the vendors have published nothing to check against. Meta alone accounts for most of that. An absent failure is not a pass: for those 1,809 requests I have no more idea who sent them than I did before I built any of this.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this does not prove
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Ten checks is ten checks.&lt;/strong&gt; The three paths received 27 requests between them; only 10 had been checked by the time I looked. The other 17 arrived before the column existed and will never be checked, because the plugin does not re-examine a request it has already processed. I am not counting them, in either direction.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A failed check is not an identification.&lt;/strong&gt; It means the address did not appear in the vendor's published list. It does not tell me who did send the request, and nothing here should be read as a claim about Anthropic, whose name was used without its involvement.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;My own list was three weeks old.&lt;/strong&gt; A verification result is only as fresh as the snapshot it compares against, and mine dated from September 3. I refreshed it on September 22 before writing this. The published ranges for Anthropic, Perplexity, Mistral, Common Crawl and DuckDuckGo had not changed by a single entry — so none of the failures above are explained by a stale list. OpenAI's had: 22 ranges added for ChatGPT-User. Which means ChatGPT-User's 35% that week is an undercount, and since checked requests are never re-checked, it is an undercount I cannot repair. It will simply be right from now on.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Verified means the company, not the crawler.&lt;/strong&gt; Anthropic publishes one combined list covering all three of its crawlers, so a match confirms the request came from Anthropic — not which of its crawlers it was.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cached pages are invisible.&lt;/strong&gt; When a page cache answers, PHP never runs and nothing is recorded. Every count here is a lower bound.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How to look at your own
&lt;/h2&gt;

&lt;p&gt;The instrument is a WordPress plugin called &lt;a href="https://wordpress.org/plugins/ailys-lens/" rel="noopener noreferrer"&gt;AILYS Lens&lt;/a&gt;, and it is free. I build it, which you should factor in. Every calculation runs on your own server, nothing is transmitted anywhere, and human visitors are never recorded.&lt;/p&gt;

&lt;p&gt;If you install it, the thing worth doing is the one I did here: read the page list first and the crawler list second. A crawler name tells you what something claims to be. A path tells you what it wanted. When a path you have never heard of appears on that list, the new column will tell you whether the name attached to it survived contact with the vendor's own records — and when it says &lt;code&gt;not checked&lt;/code&gt;, that is an honest blank rather than a zero pretending to be a verdict.&lt;/p&gt;

&lt;p&gt;I spent two articles calling this gap a defect in my own tool. It was more useful to close it than to keep describing it.&lt;/p&gt;




&lt;h3&gt;
  
  
  A note on AILYS Doctor
&lt;/h3&gt;

&lt;p&gt;AILYS Lens is free permanently. That is a design commitment rather than a pricing stage — every feature runs locally on your server, so there is nothing for a paid tier to unlock.&lt;/p&gt;

&lt;p&gt;The diagnostic service alongside it, AILYS Doctor, is a different thing. Lens tells you what happened; Doctor tells you why, and what to change. It is free to use during its data-collection period, and any future pricing will be announced on the site.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://ailys.app/en/doctor/sample" rel="noopener noreferrer"&gt;See a sample diagnosis&lt;/a&gt; · &lt;a href="https://ailys.app/en/doctor?src=devto-34-of-34-then-0-of-10" rel="noopener noreferrer"&gt;AILYS Doctor&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Illustration generated with AI and selected by the author.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>security</category>
      <category>ai</category>
      <category>webdev</category>
      <category>wordpress</category>
    </item>
    <item>
      <title>Something Asked for My .env File Twenty Times. It Said It Was ClaudeBot.</title>
      <dc:creator>roadleon</dc:creator>
      <pubDate>Tue, 22 Sep 2026 00:36:42 +0000</pubDate>
      <link>https://dev.to/roadleon/something-asked-for-my-env-file-twenty-times-it-said-it-was-claudebot-4iib</link>
      <guid>https://dev.to/roadleon/something-asked-for-my-env-file-twenty-times-it-said-it-was-claudebot-4iib</guid>
      <description>&lt;p&gt;In the last seven days, something asked this site for its &lt;code&gt;.env&lt;/code&gt; file twenty times. It said it was ClaudeBot.&lt;/p&gt;

&lt;p&gt;An &lt;code&gt;.env&lt;/code&gt; file is where a web application keeps its secrets: database passwords, API keys, cloud credentials. It is not meant to be reachable over HTTP at all. On this site it is not: the path returns a 404, as it should.&lt;/p&gt;

&lt;p&gt;No training crawler wants that file. There is nothing in it to train on. A crawler that asks for it is not reading your site; it is checking whether you left your keys in the door.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the list actually looked like
&lt;/h2&gt;

&lt;p&gt;This is the panel that shows which pages AI crawlers read, for the seven days ending September 18. I have left it in the order the dashboard shows it.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Path (requests)&lt;/th&gt;
&lt;th&gt;Top crawler&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;/&lt;/code&gt; (27)&lt;/td&gt;
&lt;td&gt;Bytespider&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;/.env&lt;/code&gt; (20)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;ClaudeBot&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;/tag/news/&lt;/code&gt; (12)&lt;/td&gt;
&lt;td&gt;Bytespider&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;/pickup-info/mypal-wifi/&lt;/code&gt; (9)&lt;/td&gt;
&lt;td&gt;ChatGPT-User&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;/tag/ココナッツ/&lt;/code&gt; (7)&lt;/td&gt;
&lt;td&gt;meta-externalagent&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;/tag/フィリピンイーグル/&lt;/code&gt; (7)&lt;/td&gt;
&lt;td&gt;meta-externalagent&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;/read-document&lt;/code&gt; (6)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;PerplexityBot&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Most of that is a small Japanese site being read the way you would expect: the front page, a few tag pages, an article about pocket wifi. Two rows are not like the others. Neither &lt;code&gt;/.env&lt;/code&gt; nor &lt;code&gt;/read-document&lt;/code&gt; exists here. Both return 404.&lt;/p&gt;

&lt;h2&gt;
  
  
  My own instrument could not tell me
&lt;/h2&gt;

&lt;p&gt;The obvious question is whether those twenty requests really came from Anthropic. My dashboard has a Verified column for exactly this, and over those seven days ClaudeBot came out at 68% verified — meaning about a third of its requests could not be confirmed as Anthropic.&lt;/p&gt;

&lt;p&gt;Which does not answer the question. The column reports a percentage per crawler, not per path. I cannot ask it whether the &lt;code&gt;/.env&lt;/code&gt; requests were among the verified ones or the unverified ones. That is the same defect I &lt;a href="https://ailys.app/en/blog/984-requests-said-perplexity" rel="noopener noreferrer"&gt;described two weeks ago&lt;/a&gt; and have still not fixed.&lt;/p&gt;

&lt;blockquote&gt;
&lt;h2&gt;
  
  
  68%
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Share of ClaudeBot requests confirmed as coming from Anthropic that week.&lt;/strong&gt;&lt;br&gt;
Which tells you nothing about the twenty that asked for the .env file. Per-path verification is the thing my dashboard is missing.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  I did not have to guess
&lt;/h2&gt;

&lt;p&gt;On August 31, the security firm GreyNoise &lt;a href="https://www.helpnetsecurity.com/2026/08/31/ai-crawlers-scan-exposed-credentials/" rel="noopener noreferrer"&gt;published what it had been watching&lt;/a&gt;. Between July 28 and August 23 it counted 824 addresses running scans that forged AI crawler names — six crawler names belonging to four AI companies, with two Amazon crawler names appearing in greater volume still. What they asked for: &lt;code&gt;/.env&lt;/code&gt;, &lt;code&gt;/.env.production&lt;/code&gt;, &lt;code&gt;/.env.bak&lt;/code&gt;, &lt;code&gt;/.aws/credentials&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The detail that stopped me is not the volume. It is the tell they found.&lt;/p&gt;

&lt;blockquote&gt;
&lt;h2&gt;
  
  
  0
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Requests for robots.txt in that entire scanning campaign.&lt;/strong&gt;&lt;br&gt;
The real ClaudeBot asks for robots.txt more than anything else — it is 12% of its traffic. The impostors never asked once.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That is a beautifully simple signature. A crawler that respects your site checks the rules first; it is the most boring thing it does, and it does it constantly. Something that wants your credentials has no reason to look at a file telling it where it may not go.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the crawler name was the target
&lt;/h2&gt;

&lt;p&gt;These names were not chosen at random. Site owners have spent the past two years being told to add AI crawlers to their allowlists — to let GPTBot and ClaudeBot and PerplexityBot through so their content gets read, or to block them deliberately if they would rather not be. Either way, the name became a thing that firewalls and plugins make decisions about.&lt;/p&gt;

&lt;p&gt;A user agent is a header the client writes about itself. It is a claim, not an identity. I demonstrated this on my own site two weeks ago by asking it for a page while calling myself ClaudeBot, and watching my dashboard record the visit as ClaudeBot. That took one line of curl. The scanners are doing the same thing, at scale, against the specific names that people have been trained to wave through.&lt;/p&gt;

&lt;p&gt;An allowlist keyed on a name that anybody can type is not a control. It is a convention being mistaken for one.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to actually do
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Do not serve secrets over HTTP, ever.&lt;/strong&gt; This is the one that matters. If &lt;code&gt;.env&lt;/code&gt; is unreachable, the scan is a wasted 404 and nothing else. Everything below is secondary to this.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Check addresses, not names.&lt;/strong&gt; OpenAI, Anthropic, Perplexity, Common Crawl and Google all publish IP ranges or reverse-DNS schemes now. If a request claims a vendor that publishes one and does not match it, the name is worth nothing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Watch for what is missing.&lt;/strong&gt; A client that never fetches robots.txt but does fetch config paths is not a crawler, whatever it calls itself. That signature costs nothing to look for.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Treat crawler traffic as traffic.&lt;/strong&gt; The habit of filing "AI crawler" away as a separate, benign category is precisely what this campaign is built to exploit.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What this instrument cannot see
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Verification is per crawler, not per path.&lt;/strong&gt; The central question of this article — were those twenty requests Anthropic or not — is one my own dashboard cannot answer. I am reporting the limit rather than writing around it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;One site, seven days.&lt;/strong&gt; Twenty requests is a tiny sample. It is consistent with a campaign that GreyNoise measured at 824 addresses; it does not independently prove anything.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;I have not attributed anything.&lt;/strong&gt; I do not know who sent these, and nothing here should be read as a claim about Anthropic, whose name was used without its involvement.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cached pages are invisible.&lt;/strong&gt; When a page cache answers, PHP never runs and nothing is recorded. Every count here is a lower bound.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How to measure your own site
&lt;/h2&gt;

&lt;p&gt;The instrument is a WordPress plugin called &lt;a href="https://wordpress.org/plugins/ailys-lens/" rel="noopener noreferrer"&gt;AILYS Lens&lt;/a&gt;, and it is free. I build it, which you should factor in. Every calculation runs on your own server, nothing is transmitted anywhere, and human visitors are never recorded. The page list described here is on the main dashboard. If a path you have never heard of appears on it, that is worth ten seconds of your attention.&lt;/p&gt;




&lt;h3&gt;
  
  
  A note on AILYS Doctor
&lt;/h3&gt;

&lt;p&gt;AILYS Lens is free permanently. That is a design commitment rather than a pricing stage — every feature runs locally on your server, so there is nothing for a paid tier to unlock.&lt;/p&gt;

&lt;p&gt;The diagnostic service alongside it, AILYS Doctor, is a different thing. Lens tells you what happened; Doctor tells you why, and what to change. It is free to use during its data-collection period, and any future pricing will be announced on the site.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://ailys.app/en/doctor/sample" rel="noopener noreferrer"&gt;See a sample diagnosis&lt;/a&gt; · &lt;a href="https://ailys.app/en/doctor?src=devto-asked-for-my-env-file" rel="noopener noreferrer"&gt;AILYS Doctor&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Illustration generated with AI and selected by the author.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>security</category>
      <category>ai</category>
      <category>webdev</category>
      <category>wordpress</category>
    </item>
    <item>
      <title>Meta Reads More Than Everyone Else Combined. None of It Was a Question.</title>
      <dc:creator>roadleon</dc:creator>
      <pubDate>Fri, 18 Sep 2026 04:42:46 +0000</pubDate>
      <link>https://dev.to/roadleon/meta-reads-more-than-everyone-else-combined-none-of-it-was-a-question-3b5</link>
      <guid>https://dev.to/roadleon/meta-reads-more-than-everyone-else-combined-none-of-it-was-a-question-3b5</guid>
      <description>&lt;p&gt;One company reads this site more than every other AI company put together. In thirty days, not one of those requests came from a person asking a question.&lt;/p&gt;

&lt;p&gt;I have been measuring a small WordPress site for a few months now, recording only AI crawlers and sorting each request by what the crawler is for. Over the thirty days ending September 11, it was visited 11,818 times, and 8,979 of those landed on pages with writing on them.&lt;/p&gt;

&lt;p&gt;Here is who came.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Vendor&lt;/th&gt;
&lt;th&gt;Share&lt;/th&gt;
&lt;th&gt;Of which, live questions&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Meta&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;52.3%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Apple&lt;/td&gt;
&lt;td&gt;12.0%&lt;/td&gt;
&lt;td&gt;0%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Anthropic&lt;/td&gt;
&lt;td&gt;9.6%&lt;/td&gt;
&lt;td&gt;6%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Perplexity&lt;/td&gt;
&lt;td&gt;8.3%&lt;/td&gt;
&lt;td&gt;93%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OpenAI&lt;/td&gt;
&lt;td&gt;7.9%&lt;/td&gt;
&lt;td&gt;21%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ByteDance&lt;/td&gt;
&lt;td&gt;5.8%&lt;/td&gt;
&lt;td&gt;0%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Common Crawl&lt;/td&gt;
&lt;td&gt;2.1%&lt;/td&gt;
&lt;td&gt;0%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Amazon&lt;/td&gt;
&lt;td&gt;1.5%&lt;/td&gt;
&lt;td&gt;0%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Google&lt;/td&gt;
&lt;td&gt;0.5%&lt;/td&gt;
&lt;td&gt;0%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;h2&gt;
  
  
  52.3%
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Share of all AI crawler traffic on this site that was Meta.&lt;/strong&gt;&lt;br&gt;
Everyone else, added together, accounts for 47.7%.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The first column is the one people quote. The second column is the one that decides whether any of it can ever reach you.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three crawlers, three different jobs
&lt;/h2&gt;

&lt;p&gt;Meta does not run one crawler. It runs several, and it says plainly what each is for. Three of them are AI crawlers, and this instrument watches all three.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;meta-externalagent&lt;/code&gt;&lt;/strong&gt; — Meta's documentation says it "crawls the web for use cases such as training foundation AI models or improving products by indexing content directly."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;meta-webindexer&lt;/code&gt;&lt;/strong&gt; — it "navigates the web to improve Meta AI search result quality for users," which is the crawler whose job includes getting citations right.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;meta-externalfetcher&lt;/code&gt;&lt;/strong&gt; — it fetches individual links at a user's request. This is the one that means somebody asked something a few seconds ago.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Over thirty days, the split between them was not close.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Meta crawler&lt;/th&gt;
&lt;th&gt;Requests&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;meta-externalagent (training)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;6,027&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;meta-webindexer (answer quality, citations)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;155&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;meta-externalfetcher (a person asked)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Thirty-nine requests collecting material for a model, for every one request improving the thing that could cite you. And the third row is not small. It is empty.&lt;/p&gt;

&lt;blockquote&gt;
&lt;h2&gt;
  
  
  0
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Requests from Meta's user-initiated fetcher, in thirty days.&lt;/strong&gt;&lt;br&gt;
It did not appear once. Five other vendors run one — OpenAI, Perplexity, Anthropic, DuckDuckGo, Mistral — and all five of them appeared.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The path back exists. It is barely used.
&lt;/h2&gt;

&lt;p&gt;This is what makes the ratio worth looking at rather than just complaining about. Meta is not a company without a route from its answers to your site. It documents one. &lt;code&gt;meta-webindexer&lt;/code&gt; exists precisely so that Meta AI can search well and attribute accurately.&lt;/p&gt;

&lt;p&gt;On this site, that crawler did 155 requests while the training collector did 6,027. The machinery that might send a reader back is running at about two percent of the machinery that takes material away.&lt;/p&gt;

&lt;p&gt;Compare the shape of that with the two vendors at the other end. Perplexity's traffic here is 93% user-initiated: almost everything it does on this site is a person, right now, waiting for an answer. OpenAI is 21%. Meta is zero.&lt;/p&gt;

&lt;p&gt;That is not a moral ranking. A training crawler is not doing anything wrong by collecting training data; that is its job, and Meta says so in the open. But the mix tells you what a given company currently wants from your writing, and the mixes are not remotely alike.&lt;/p&gt;

&lt;h2&gt;
  
  
  An awkward footnote: I cannot prove any of this was Meta
&lt;/h2&gt;

&lt;p&gt;Everything above rests on 6,182 requests that said they were Meta. Meta publishes no IP ranges and no reverse-DNS scheme for these crawlers, so there is no way to confirm that any single one of them came from Meta. My dashboard marks the entire vendor with a dash where other vendors have a percentage.&lt;/p&gt;

&lt;p&gt;So the largest reader of this site is also the one I can least verify. I &lt;a href="https://ailys.app/en/blog/984-requests-said-perplexity" rel="noopener noreferrer"&gt;wrote about that problem&lt;/a&gt; a week ago and it has not improved: most requests on this site still arrive with no proof of who sent them, and none of Meta's ever can.&lt;/p&gt;

&lt;p&gt;I do not think these requests are forged. The volume is steady, the behaviour is consistent, and there is no obvious reason to impersonate a crawler that most site owners have never heard of. But I am reporting a number I am not able to check, and that should be said out loud rather than buried.&lt;/p&gt;

&lt;h2&gt;
  
  
  Which brings us back to the two
&lt;/h2&gt;

&lt;p&gt;In the &lt;a href="https://ailys.app/en/blog/read-10202-visited-twice" rel="noopener noreferrer"&gt;first piece I wrote&lt;/a&gt;, this site had been read about ten thousand times in a month and had received two human arrivals, one from ChatGPT and one from DuckDuckGo.&lt;/p&gt;

&lt;p&gt;I did not know it at the time, but the single largest contributor to that reading number was the one vendor structurally least likely to appear in the arrival number. The gap between "read" and "visited" was not evenly caused. It had a majority shareholder.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this does not mean
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;It does not mean Meta never fetches on request.&lt;/strong&gt; It means that on this site, in these thirty days, its fetcher did not appear. One site is not a census.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It does not mean Meta never cites anyone.&lt;/strong&gt; Meta runs a crawler whose documented job is citation quality. It ran here 155 times. What I can say is the ratio, not the policy behind it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It does not mean training collection is theft.&lt;/strong&gt; That argument exists and it is not this article. This article is about what the numbers say a company is doing, which is a smaller and more checkable claim.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;52.3% is of the crawlers this instrument tracks.&lt;/strong&gt; Meta also runs crawlers for ads and for link previews. Those are not AI crawlers and are not counted here.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What this instrument cannot see
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Cached pages are invisible.&lt;/strong&gt; When a page cache answers, PHP never runs and nothing is recorded. Every count here is a lower bound.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;One site, one month, one language.&lt;/strong&gt; This is a Japanese site about a city in the Philippines. A different subject, a different language, a different publishing rhythm would draw a different table.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Arrivals are undercounted by design.&lt;/strong&gt; AI clients running as phone or desktop apps send no referrer, so a reader who does follow a link can land in your analytics as direct traffic. The arrival number is a floor.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How to measure your own site
&lt;/h2&gt;

&lt;p&gt;The instrument is a WordPress plugin called &lt;a href="https://wordpress.org/plugins/ailys-lens/" rel="noopener noreferrer"&gt;AILYS Lens&lt;/a&gt;, and it is free. I build it, which you should factor in. Every calculation runs on your own server, nothing is transmitted anywhere, and human visitors are never recorded. The vendor table described here is the main dashboard, and the split by purpose — training, answer indexing, live fetch — is the column worth looking at second.&lt;/p&gt;

&lt;p&gt;You may find the same shape. You may find the opposite. The point is that this is a question with a factual answer about your own site, and it takes a week to get.&lt;/p&gt;




&lt;h3&gt;
  
  
  A note on AILYS Doctor
&lt;/h3&gt;

&lt;p&gt;AILYS Lens is free permanently. That is a design commitment rather than a pricing stage — every feature runs locally on your server, so there is nothing for a paid tier to unlock.&lt;/p&gt;

&lt;p&gt;The diagnostic service alongside it, AILYS Doctor, is a different thing. Lens tells you what happened; Doctor tells you why, and what to change. It is free to use during its data-collection period, and any future pricing will be announced on the site.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://ailys.app/en/doctor/sample" rel="noopener noreferrer"&gt;See a sample diagnosis&lt;/a&gt; · &lt;a href="https://ailys.app/en/doctor?src=devto-meta-reads-more-none-was-a-question" rel="noopener noreferrer"&gt;AILYS Doctor&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Illustration generated with AI and selected by the author.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>seo</category>
      <category>wordpress</category>
    </item>
    <item>
      <title>2,894 Requests Hit My Top Page. Nobody Ever Requested It.</title>
      <dc:creator>roadleon</dc:creator>
      <pubDate>Fri, 11 Sep 2026 06:28:41 +0000</pubDate>
      <link>https://dev.to/roadleon/2894-requests-hit-my-top-page-nobody-ever-requested-it-2pib</link>
      <guid>https://dev.to/roadleon/2894-requests-hit-my-top-page-nobody-ever-requested-it-2pib</guid>
      <description>&lt;p&gt;The most-read page last month, on a site I look after, was a URL that nobody ever requested.&lt;/p&gt;

&lt;p&gt;I did not set out to find that. I was going to write about Meta.&lt;/p&gt;

&lt;p&gt;Meta's crawlers account for 43% of all AI crawler traffic on the site I have been measuring — more than OpenAI, Anthropic and Perplexity combined. That is a strange enough fact to want an explanation, so I opened the panel that lists which pages the crawlers actually read, expecting to see what Meta was so interested in.&lt;/p&gt;

&lt;p&gt;The top row was &lt;code&gt;/tag//&lt;/code&gt;. Two slashes. 2,894 requests in thirty days, most of them from Meta's crawler, which is more than half of everything that crawler did on the site.&lt;/p&gt;

&lt;h2&gt;
  
  
  A better story than the one I had
&lt;/h2&gt;

&lt;p&gt;So I loaded it. &lt;code&gt;/tag//&lt;/code&gt; returns a 301 to &lt;code&gt;/tag/&lt;/code&gt;, and &lt;code&gt;/tag/&lt;/code&gt; returns a 404. A redirect to a dead end.&lt;/p&gt;

&lt;p&gt;For about ten minutes this was a much better article than the one I had planned. The largest AI crawler on this site was spending half of its visits hammering a URL that goes nowhere, ninety-six times a day, and the site owner had no way to know. Write that down, publish it, done.&lt;/p&gt;

&lt;p&gt;Before writing it I wanted to know where the crawler had found the URL. That is where it fell apart.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The site's home page contains 29 links to tag pages. Every one of them is a well-formed absolute URL. None has a double slash.&lt;/li&gt;
&lt;li&gt;The sitemap lists 276 tag URLs. None has a double slash, and &lt;code&gt;/tag/&lt;/code&gt; itself is not in it.&lt;/li&gt;
&lt;li&gt;The 301 carries &lt;code&gt;x-redirect-by: WordPress&lt;/code&gt;, so PHP is running on every one of those requests. Nothing is being cached or shortcut.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A crawler does not invent URLs out of nothing, and there was nothing here to find it in. Which left one place I had not looked.&lt;/p&gt;

&lt;h2&gt;
  
  
  The call was coming from inside the house
&lt;/h2&gt;

&lt;p&gt;My own recording code, one line of it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sanitize_text_field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$path&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://developer.wordpress.org/reference/functions/sanitize_text_field/" rel="noopener noreferrer"&gt;&lt;code&gt;sanitize_text_field()&lt;/code&gt;&lt;/a&gt; is the reflexive way to clean a string in WordPress. It appears in every tutorial and most plugins. Its documentation lists five things it does. Four of them are what you expect: checks for invalid UTF-8, converts a bare &lt;code&gt;&amp;lt;&lt;/code&gt; to an entity, strips tags, collapses whitespace.&lt;/p&gt;

&lt;p&gt;The fifth is &lt;em&gt;"Strips percent-encoded characters."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Five words, no explanation, and no warning anywhere on the page about what that means for a URL. Here is what it means for a URL:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;What the crawler requested&lt;/th&gt;
&lt;th&gt;What I recorded&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/tag/%e5%9f%b4%e8%bc%aa/&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/tag//&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/topics/%e3%83%8b%e3%83%a5%e3%83%bc%e3%82%b9/&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/topics//&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/tag/dabawenyo/&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/tag/dabawenyo/&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The site is Japanese. Its tag slugs are Japanese, so in a URL they are percent-encoded, so my sanitiser deleted them. Every Japanese tag page on the site was being written to the database as the same six characters, and then grouped together by a &lt;code&gt;GROUP BY url_path&lt;/code&gt; that was doing exactly what it was told.&lt;/p&gt;

&lt;p&gt;Meta was not stuck. Meta was reading hundreds of different tag pages, perfectly normally. I was the one turning them into one row.&lt;/p&gt;

&lt;blockquote&gt;
&lt;h2&gt;
  
  
  0
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Times anyone requested the URL at the top of my most-read-pages list.&lt;/strong&gt;&lt;br&gt;
It was not a page. It was 2,894 requests to many different pages, with the distinguishing part deleted.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Who else this happens to
&lt;/h2&gt;

&lt;p&gt;Everyone whose URLs are not ASCII.&lt;/p&gt;

&lt;p&gt;A slug in Japanese, Chinese, Korean, Thai, Greek, Hebrew, Arabic, or any Cyrillic language is percent-encoded in a URL. So is a French or Portuguese or Turkish slug with an accent in it. On all of those sites, any tool that runs a path through &lt;code&gt;sanitize_text_field()&lt;/code&gt; is storing a path with the identifying part removed — and storing it silently, in a form that still looks like a path.&lt;/p&gt;

&lt;p&gt;The function is not broken. Stripping percent sequences is a reasonable defence for a free-text field, where an encoded payload has no business being. It is only catastrophic when the field &lt;em&gt;is&lt;/em&gt; percent-encoded on purpose, which is to say whenever it is a URL. The documentation states the behaviour and leaves you to work out the consequence.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this kind of bug survives
&lt;/h2&gt;

&lt;p&gt;I had been looking at that dashboard for weeks. So had the site's owner — I manage it, they own it. Neither of us noticed, and I want to be precise about why, because the reason generalises past this one function.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;It never threw anything.&lt;/strong&gt; No error, no warning, no empty result. A crash on day one would have cost me an afternoon.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The output was plausible.&lt;/strong&gt; &lt;code&gt;/tag//&lt;/code&gt; looks like a slightly malformed URL, which is a thing that genuinely happens. It reads as a finding, not as a defect. I nearly published it as one.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It failed by merging, not by losing.&lt;/strong&gt; No request was dropped. The totals were all correct. Damage that shows up as a smaller number gets noticed; damage that shows up as one confident large number does not.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It only affects other people's alphabets.&lt;/strong&gt; Every test I had written used ASCII paths, because I wrote them. On an English site this code is flawless.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That last one is the part I keep turning over. The bug was invisible to me specifically, in a way it would not have been to someone who tested in their own language first.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to do instead
&lt;/h2&gt;

&lt;p&gt;If you are storing a whole URL, WordPress has &lt;code&gt;esc_url_raw()&lt;/code&gt; for it. If you are storing a path and you want it to survive intact, do the work explicitly rather than reaching for the general-purpose cleaner: reject invalid UTF-8, strip control characters, cap the length, and leave the percent sequences alone. That is roughly ten lines, and it is the ten lines I should have written the first time.&lt;/p&gt;

&lt;p&gt;Then write a test with a non-ASCII path in it. Mine now has four, and they exist for exactly one reason, which is that I will not remember this in a year.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this did and did not break
&lt;/h2&gt;

&lt;p&gt;Being specific matters here, because I have published numbers from this instrument twice and I would rather say plainly which ones this touches.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Broken: which pages were read.&lt;/strong&gt; On any non-English site, that panel has been unusable. Not subtly wrong — unusable.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Not broken: how much was read.&lt;/strong&gt; The path is used in one other place, a check that decides whether a request counts as a content page. That check is a list of exclusions — robots.txt, sitemaps, static file extensions, login and admin endpoints — and every one of them is ASCII. A Japanese path is classified the same before and after the damage.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Not broken: anything else.&lt;/strong&gt; Request counts, the split by crawler purpose, the verified percentages: none of them touch the path.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The fix went in on September 6. Old records cannot be recovered — the deleted bytes are gone — so the page list will be correct from that date and wrong before it, which is its own small ongoing embarrassment.&lt;/p&gt;

&lt;h2&gt;
  
  
  And Meta?
&lt;/h2&gt;

&lt;p&gt;Still 43% of everything, still unexplained, still crawling more of this site than OpenAI, Anthropic and Perplexity put together. I will get to it. It turns out I did not have the instrument to write that article yet, and I would not have known that if the top row of my own dashboard had been slightly less strange.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to measure your own site
&lt;/h2&gt;

&lt;p&gt;The instrument is a WordPress plugin called &lt;a href="https://wordpress.org/plugins/ailys-lens/" rel="noopener noreferrer"&gt;AILYS Lens&lt;/a&gt;, and it is free. I build it, which by now you have enough evidence to factor in. Every calculation runs on your own server, nothing is transmitted anywhere, and human visitors are never recorded. If you run it on a site whose URLs are not in English, update it — the page list before this fix was telling you nothing.&lt;/p&gt;




&lt;h3&gt;
  
  
  A note on AILYS Doctor
&lt;/h3&gt;

&lt;p&gt;AILYS Lens is free permanently. That is a design commitment rather than a pricing stage — every feature runs locally on your server, so there is nothing for a paid tier to unlock.&lt;/p&gt;

&lt;p&gt;The diagnostic service alongside it, AILYS Doctor, is a different thing. Lens tells you what happened; Doctor tells you why, and what to change. It is free to use during its data-collection period, and any future pricing will be announced on the site.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Updated September 15, 2026: this note originally said Doctor would become a paid subscription on that date. The change has been postponed, and Doctor remains free for now.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://ailys.app/en/doctor/sample" rel="noopener noreferrer"&gt;See a sample diagnosis&lt;/a&gt; · &lt;a href="https://ailys.app/en/doctor?src=devto-2894-requests-nobody-requested" rel="noopener noreferrer"&gt;AILYS Doctor&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Illustration generated with AI and selected by the author.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>wordpress</category>
      <category>php</category>
      <category>webdev</category>
      <category>ai</category>
    </item>
    <item>
      <title>984 Requests Said They Were Perplexity. None Could Prove It.</title>
      <dc:creator>roadleon</dc:creator>
      <pubDate>Sun, 06 Sep 2026 01:32:13 +0000</pubDate>
      <link>https://dev.to/roadleon/984-requests-said-they-were-perplexity-none-could-prove-it-33nm</link>
      <guid>https://dev.to/roadleon/984-requests-said-they-were-perplexity-none-could-prove-it-33nm</guid>
      <description>&lt;p&gt;This morning I told my own website that I was ClaudeBot.&lt;/p&gt;

&lt;p&gt;It took one line of curl and a user agent string copied out of Anthropic's own documentation. Three requests to one article page. Then I opened my dashboard: Anthropic, ClaudeBot, training — 1,698 requests had become 1,701, last seen at 09:22.&lt;/p&gt;

&lt;p&gt;None of them was ClaudeBot. They came from a laptop in Japan, over ordinary home broadband, from an address that appears on no crawler list anywhere.&lt;/p&gt;

&lt;p&gt;The instrument was not broken. It was doing exactly what it is built to do, which is to write down what arrived and what it called itself. A user agent is a claim. Anyone can type it. The whole question is how much of what you are measuring is a claim you can check, and the honest answer is worse than I expected — including, as it turned out, in ways that were my own fault.&lt;/p&gt;

&lt;h2&gt;
  
  
  The column nobody looks at
&lt;/h2&gt;

&lt;p&gt;There is a way to check, for some of them. A vendor can publish the IP addresses its crawlers run from. If a request claiming to be GPTBot arrives from an address on OpenAI's published list, it is GPTBot. If it arrives from my laptop, it is not. Some vendors publish such a list. Some publish nothing at all. So my dashboard has a column called Verified, and it is the least glamorous number on the page.&lt;/p&gt;

&lt;p&gt;Here is one site, davaonline.net, over the thirty days ending September 4, 2026: 13,491 crawler requests, of which 10,390 landed on pages with actual writing on them. Sorted by volume, with the share of each that could be confirmed:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Crawler (requests)&lt;/th&gt;
&lt;th&gt;Verified&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;meta-externalagent (5,728)&lt;/td&gt;
&lt;td&gt;&lt;em&gt;no method published&lt;/em&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ClaudeBot (1,698)&lt;/td&gt;
&lt;td&gt;&lt;em&gt;no method published&lt;/em&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Applebot (1,479)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;98%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Perplexity-User (984)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ChatGPT-User (887)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;6%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bytespider (767)&lt;/td&gt;
&lt;td&gt;&lt;em&gt;no method published&lt;/em&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GPTBot (598)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;81%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CCBot (440)&lt;/td&gt;
&lt;td&gt;&lt;em&gt;no method published&lt;/em&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Amazonbot (342)&lt;/td&gt;
&lt;td&gt;&lt;em&gt;no method published&lt;/em&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OAI-SearchBot (242)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;60%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;meta-webindexer (110)&lt;/td&gt;
&lt;td&gt;&lt;em&gt;no method published&lt;/em&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PerplexityBot (103)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Claude-User (69)&lt;/td&gt;
&lt;td&gt;&lt;em&gt;no method published&lt;/em&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GoogleOther (25)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;100%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Claude-SearchBot (19)&lt;/td&gt;
&lt;td&gt;&lt;em&gt;no method published&lt;/em&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;h2&gt;
  
  
  1 in 6
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Requests that arrived with proof of who sent them.&lt;/strong&gt;&lt;br&gt;
Roughly 2,200 confirmed out of 13,491. Everything else is a string I wrote down because it was handed to me.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That single number hides three completely different failures, and separating them is the only useful thing I can do here.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;All 13,491 requests, by why verification did or did not happen&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;No verification method published by the vendor&lt;/td&gt;
&lt;td&gt;6,947&lt;/td&gt;
&lt;td&gt;51%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Published, but my bundled copy was out of date&lt;/td&gt;
&lt;td&gt;2,226&lt;/td&gt;
&lt;td&gt;16%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Published, I had it, and the address did not match&lt;/td&gt;
&lt;td&gt;4,318&lt;/td&gt;
&lt;td&gt;32%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Failure one: nothing to check against
&lt;/h2&gt;

&lt;p&gt;Meta, ByteDance and Amazon publish no IP ranges and no reverse-DNS scheme for the crawlers in that table. There is no list to compare an address to. Not a hard case, not an unlucky one — there is simply no mechanism, so verification is not slow or partial, it is impossible.&lt;/p&gt;

&lt;p&gt;That accounts for 6,947 of the 13,491 requests. More than half the traffic in this table comes from vendors who have decided that site owners do not need a way to tell whether it is really them. The largest single crawler on the site, meta-externalagent at 5,728 requests, is in this group.&lt;/p&gt;

&lt;p&gt;Amazon is a near miss worth naming: it does publish a page of addresses for Amazonbot, but as a web page whose contents only appear after JavaScript runs. A list a machine cannot read is a list that does not exist for this purpose.&lt;/p&gt;

&lt;p&gt;And a small trap for anyone else doing this: ByteDance's obvious candidate URL returns HTTP 200. The body is a 404 page. If you check for a published list by looking at status codes, you will conclude they publish one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Failure two: the list existed and I was out of date
&lt;/h2&gt;

&lt;p&gt;Look at the Anthropic rows and the CCBot row in that table. They say no method published. That was true when the plugin's bundled snapshot of vendor ranges was taken, on July 20. It is not true now.&lt;/p&gt;

&lt;p&gt;Anthropic began publishing a range list on August 18, replacing an explicit statement that it did not. Common Crawl published one on August 11. Both landed inside the thirty days measured above, and my instrument went on reporting the old world, because a design decision I am otherwise happy with — the plugin never talks to the network, so vendor ranges ship as a snapshot — means the snapshot goes stale silently.&lt;/p&gt;

&lt;p&gt;That is 2,226 requests, sixteen percent of the total, reported as unverifiable when they were merely unverified by me. I fixed it on September 4; the count of crawlers this instrument can verify went from eleven to fifteen. The fix also caught a Google crawler that had been listed as verifiable for months while its ranges were never actually bundled.&lt;/p&gt;

&lt;p&gt;I would rather not have found this while writing an article about verification. But it is the more useful half of the lesson: whether a crawler is verifiable is not a property of the crawler. It is a property of how fresh your copy of somebody else's list is, and it decays without telling you.&lt;/p&gt;

&lt;h2&gt;
  
  
  Failure three: the list existed, I had it, and it did not match
&lt;/h2&gt;

&lt;p&gt;This is the one I cannot explain away.&lt;/p&gt;

&lt;p&gt;Perplexity publishes IP ranges. I had them. Over thirty days, 984 requests arrived calling themselves Perplexity-User and 103 calling themselves PerplexityBot. The share of them coming from an address on Perplexity's own published list was not low.&lt;/p&gt;

&lt;blockquote&gt;
&lt;h2&gt;
  
  
  0
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Requests claiming to be Perplexity that came from Perplexity's published addresses.&lt;/strong&gt;&lt;br&gt;
Out of 1,087, over thirty days, checked against a list I had.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;OpenAI publishes too, and keeps it current — its ChatGPT-User list was refreshed on August 14. Six percent of the 887 requests claiming to be ChatGPT-User came from it.&lt;/p&gt;

&lt;p&gt;There are at least four explanations and I can rule out none of them:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The requests are not from those vendors.&lt;/strong&gt; Forging a user agent costs one line of curl, as established at the top of this article. Somebody scraping the web has an obvious incentive to wear a name that site owners have been told to allow.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The published list is incomplete.&lt;/strong&gt; Perplexity's user-fetch list contains four address ranges and was last updated in October 2025. Its crawler list contains eight, last touched in February 2025. A company serving live questions at scale from four ranges, unchanged for eleven months, is at least worth a raised eyebrow.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Real fetches run outside the published infrastructure.&lt;/strong&gt; User-initiated fetching often runs from different machines than bulk crawling, and nothing obliges a vendor to put those in the same file.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;My own verification did not run.&lt;/strong&gt; Addresses are checked by an hourly background job that processes a bounded batch and discards anything older than a day. A request that was never checked and a request that was checked and failed both appear in that column as unverified. My dashboard does not currently tell them apart, which on reflection is a defect and not a detail.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The last one deserves the discomfort it causes. I can report the share of traffic I confirmed. I cannot report the share I disproved. Those are different quantities, and an instrument that reports only the first should say so out loud rather than let a low percentage imply an accusation.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you can still conclude
&lt;/h2&gt;

&lt;p&gt;Less than you would like, but not nothing, and the shape of it is stable.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The verified count is a floor on real crawler activity and never a ceiling. Every failure mode above pushes the same way: it turns real visits into unconfirmed ones, never the reverse.&lt;/li&gt;
&lt;li&gt;The unverified count is not an accusation. Most of it is vendors who published nothing, which is a policy choice on their side, not evidence about the request.&lt;/li&gt;
&lt;li&gt;The gap is worth watching per vendor rather than in aggregate. A vendor that publishes ranges and still matches nothing is telling you something. A vendor that publishes nothing is telling you only that it published nothing.&lt;/li&gt;
&lt;li&gt;None of this changes the finding from &lt;a href="https://ailys.app/en/blog/read-10202-visited-twice" rel="noopener noreferrer"&gt;last week's piece&lt;/a&gt;: this site was read roughly ten thousand times and visited twice. Whether every reader was who it claimed to be does not make the reading less real.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What this instrument cannot see
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;One site, one month.&lt;/strong&gt; A different site, in a different language, on different hosting, would draw a different table. This is an instrument reading, not a study.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cached pages are invisible.&lt;/strong&gt; When a page cache answers, PHP never runs and nothing is recorded. Every count here is a lower bound.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The percentages are rounded.&lt;/strong&gt; The dashboard reports whole percentages, so the "roughly 2,200" above is derived from rounded figures and should be read as approximate.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Verification confirms a vendor, not a crawler.&lt;/strong&gt; Anthropic publishes one combined list for all three of its crawlers, so a confirmed hit means the request came from Anthropic, not which of its crawlers it was.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The uncomfortable part is not that some crawlers cannot be verified. It is that the line between the ones that can and the ones that cannot moved twice last month, in my favour both times, and my instrument did not notice until I went looking for something to write about. If you are running any measurement of this kind, the question is not whether your numbers are right today. It is when you last checked whether the world they describe still exists.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to measure your own site
&lt;/h2&gt;

&lt;p&gt;The instrument is a WordPress plugin called &lt;a href="https://wordpress.org/plugins/ailys-lens/" rel="noopener noreferrer"&gt;AILYS Lens&lt;/a&gt;, and it is free. I build it, which you should factor into everything above — including the parts where I am the one who got it wrong. Every calculation runs on your own server, nothing is transmitted anywhere, and human visitors are never recorded. The Verified column described here is on the main dashboard, and vendors with no published method are marked as such rather than folded into a single number.&lt;/p&gt;




&lt;h3&gt;
  
  
  A note on AILYS Doctor
&lt;/h3&gt;

&lt;p&gt;AILYS Lens is free permanently. That is a design commitment rather than a pricing stage — every feature runs locally on your server, so there is nothing for a paid tier to unlock.&lt;/p&gt;

&lt;p&gt;The diagnostic service alongside it, AILYS Doctor, is a different thing. Lens tells you what happened; Doctor tells you why, and what to change. It is free to use during its data-collection period, and any future pricing will be announced on the site.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Updated September 15, 2026: this note originally said Doctor would become a paid subscription on that date. The change has been postponed, and Doctor remains free for now.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://ailys.app/en/doctor/sample" rel="noopener noreferrer"&gt;See a sample diagnosis&lt;/a&gt; · &lt;a href="https://ailys.app/en/doctor?src=devto-984-requests-said-perplexity" rel="noopener noreferrer"&gt;AILYS Doctor&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Illustration generated with AI and selected by the author.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>seo</category>
      <category>wordpress</category>
    </item>
    <item>
      <title>Read 10,202 times. Visited twice.</title>
      <dc:creator>roadleon</dc:creator>
      <pubDate>Thu, 03 Sep 2026 01:02:41 +0000</pubDate>
      <link>https://dev.to/roadleon/read-10202-times-visited-twice-mmi</link>
      <guid>https://dev.to/roadleon/read-10202-times-visited-twice-mmi</guid>
      <description>&lt;p&gt;The site looked dead. Thirty days of analytics, and the traffic arriving from AI came to two visits — one from ChatGPT, one from DuckDuckGo. Two. From a dashboard like that you would conclude that no AI has ever noticed the site exists.&lt;/p&gt;

&lt;p&gt;That conclusion is wrong by about ten thousand.&lt;/p&gt;

&lt;p&gt;Over the same thirty days, AI crawlers requested pages from that site 13,306 times, and 10,202 of those requests were for pages with actual writing on them. The two instruments are not contradicting each other. They are answering different questions, and almost everybody is only asking the first one.&lt;/p&gt;

&lt;p&gt;The site is davaonline.net, a WordPress site running a plugin that does the opposite of normal analytics. It ignores human visitors completely and records only AI crawlers: which ones arrived, what they fetched, and whether the thing they fetched was a page or just a file.&lt;/p&gt;

&lt;p&gt;Here is the whole month.&lt;/p&gt;

&lt;h2&gt;
  
  
  The numbers
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Thirty days&lt;/th&gt;
&lt;th&gt;Count&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;AI crawler requests&lt;/td&gt;
&lt;td&gt;13,306&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Requests that hit a content page&lt;/td&gt;
&lt;td&gt;10,202&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Requests made to answer a live question&lt;/td&gt;
&lt;td&gt;1,902&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;People who arrived by following an AI answer&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Read it downward. The story is in the descent.&lt;/p&gt;

&lt;h2&gt;
  
  
  Visiting is not reading
&lt;/h2&gt;

&lt;p&gt;The gap between the first two numbers — 3,104 requests — is not rounding error. It is robots.txt, sitemap.xml, and a tail of files that are not your writing. A crawler that fetches robots.txt and leaves has visited your site and read nothing. Counting that as &lt;em&gt;AI is reading me&lt;/em&gt; is counting a knock at the door as a conversation.&lt;/p&gt;

&lt;p&gt;Which is why the instrument sorts every request by purpose instead of reporting one number:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;th&gt;Requests (vs previous 30 days)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Training — collected to train a model&lt;/td&gt;
&lt;td&gt;9,514 (+1,175%)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AI search — indexed for an answer engine&lt;/td&gt;
&lt;td&gt;1,890 (+432%)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;User fetch — retrieved to answer a live question&lt;/td&gt;
&lt;td&gt;1,902 (+3,558%)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;These are not interchangeable. Training collection is a bet on the future: your words may end up inside a model that ships next year, or they may not. Search indexing is a bet on retrieval: you become findable inside somebody else's answer engine. User fetch is neither a bet nor a future. It means a person asked a question a few seconds ago, and a machine went to your page to answer them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reading is not traffic
&lt;/h2&gt;

&lt;p&gt;1,902 times in thirty days, this site was contacted by a crawler that retrieves on behalf of somebody asking a question right now. Two people arrived.&lt;/p&gt;

&lt;p&gt;One came from ChatGPT. One came from DuckDuckGo. That is the complete AI referral traffic for the month.&lt;/p&gt;

&lt;p&gt;A note on the arithmetic: the 1,902 and the 10,202 slice the same pile of requests in different directions — one by what the crawler is for, the other by what it asked for. Neither is contained in the other, and the 1,902 includes some share of robots.txt fetches rather than pages. Read at its most conservative, the ratio does not soften.&lt;/p&gt;

&lt;p&gt;This gap is not a funnel to optimize. It is the shape of the thing. The page was retrieved, read, and used as the source of an answer that appeared somewhere else entirely. The answer was delivered. The visit never happened. Meanwhile those fetches grew 3,558% over the previous thirty days — two arrivals is what that growth looks like from the traffic side.&lt;/p&gt;

&lt;p&gt;A conventional analytics dashboard reports this month as though almost nothing occurred. It is not wrong about visits. It is answering a question that has quietly stopped being the important one.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this instrument cannot see
&lt;/h2&gt;

&lt;p&gt;Before any of that means anything, here is what these numbers are not.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Two is a floor, not a count.&lt;/strong&gt; Arrivals are identified by referrer. AI clients running as desktop or phone apps frequently send none at all, and those visits land in your analytics as direct traffic, indistinguishable from somebody typing your URL. The true figure is higher than two. Nobody can tell you how much higher.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cached pages are invisible.&lt;/strong&gt; When a page cache answers a request, PHP never runs, so nothing is recorded. Every number here is a lower bound.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Not every crawler can be verified.&lt;/strong&gt; OpenAI and Perplexity publish IP ranges, so their requests can be confirmed. Meta and ByteDance publish nothing, so theirs are matched on the user-agent string alone — and user-agent strings can be forged. Anthropic sits in between: it began publishing a range list on August 18, partway through the month measured here, and the plugin was still carrying an older snapshot that did not contain it — so its requests are counted here as unverified as well. The dashboard marks which is which. This article does not separate them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;One site, one month.&lt;/strong&gt; Different subject matter, a different language, a different publishing rhythm would all produce different numbers. This is an instrument reading, not a study.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of those caveats move the conclusion, because they all push the same direction. Correcting for every one of them would make the reading number larger and leave the arrival number roughly where it is.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to measure your own site
&lt;/h2&gt;

&lt;p&gt;The instrument is a WordPress plugin called &lt;a href="https://wordpress.org/plugins/ailys-lens/" rel="noopener noreferrer"&gt;AILYS Lens&lt;/a&gt;, and it is free. I build it, which you should factor into everything above. It is also built so that measuring costs you nothing else: every calculation runs on your own server, nothing is transmitted anywhere, and human visitors are never recorded — only AI crawlers are. Requests that are not crawlers pass through in under a millisecond, and no CSS or JavaScript is loaded on your front end.&lt;/p&gt;

&lt;p&gt;Install it, leave it alone for a week, and you will know whether anything is reading you. It is a question with a factual answer, and until recently there was no instrument that would give it to you.&lt;/p&gt;

&lt;p&gt;The uncomfortable part of this month is not the two. It is that a site can be read ten thousand times and still produce a dashboard that looks like failure — and that its owner has no ordinary way to tell the difference between being ignored and being read in silence. That is a strange position to publish from. It seems worth knowing which one you are in.&lt;/p&gt;




&lt;h3&gt;
  
  
  A note on AILYS Doctor
&lt;/h3&gt;

&lt;p&gt;AILYS Lens is free permanently. That is a design commitment rather than a pricing stage — every feature runs locally on your server, so there is nothing for a paid tier to unlock.&lt;/p&gt;

&lt;p&gt;The diagnostic service alongside it, AILYS Doctor, is a different thing. Lens tells you what happened; Doctor tells you why, and what to change. It is free to use during its data-collection period, and any future pricing will be announced on the site.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Updated September 15, 2026: this note originally said Doctor would become a paid subscription on that date. The change has been postponed, and Doctor remains free for now.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://ailys.app/en/doctor/sample" rel="noopener noreferrer"&gt;See a sample diagnosis&lt;/a&gt; · &lt;a href="https://ailys.app/en/doctor?src=devto-read-10202-visited-twice" rel="noopener noreferrer"&gt;AILYS Doctor&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Correction, September 4, 2026: this article originally said that Anthropic publishes no IP ranges. Anthropic began publishing them on August 18, 2026, before this article went up. The passage above has been rewritten. The counts themselves are unchanged — the requests really were recorded unverified — but the reason was a stale snapshot on my side, not an absent list on theirs.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>seo</category>
      <category>wordpress</category>
    </item>
  </channel>
</rss>
