<?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: Josh White</title>
    <description>The latest articles on DEV Community by Josh White (@joshuawhite389).</description>
    <link>https://dev.to/joshuawhite389</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%2F893887%2F4c7dcb8d-9f10-4a49-8379-b0da448ba91f.jpeg</url>
      <title>DEV Community: Josh White</title>
      <link>https://dev.to/joshuawhite389</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/joshuawhite389"/>
    <language>en</language>
    <item>
      <title>Can LLMs read robots.txt? Easily. Can they write one? Only one of nine got every policy right.</title>
      <dc:creator>Josh White</dc:creator>
      <pubDate>Mon, 12 Oct 2026 00:26:09 +0000</pubDate>
      <link>https://dev.to/joshuawhite389/can-llms-read-robotstxt-easily-can-they-write-one-only-one-of-nine-got-every-policy-right-2fie</link>
      <guid>https://dev.to/joshuawhite389/can-llms-read-robotstxt-easily-can-they-write-one-only-one-of-nine-got-every-policy-right-2fie</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/kaggle-2026-09-23"&gt;Kaggle Benchmarking Challenge&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;How this was made:&lt;/strong&gt; I built this with an AI coding agent (Claude). It wrote most of the code and the first draft of this post. I reviewed the code, reran the checks and edited the post, and I stand behind every number. All numbers come from the Kaggle runs linked below.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  What I Benchmarked
&lt;/h2&gt;

&lt;p&gt;Every web crawler, from Googlebot to the AI agents now browsing on our behalf, is supposed to check a site's &lt;code&gt;robots.txt&lt;/code&gt; before fetching a page. Site owners are writing more of these files than ever, often with a chatbot's help, to keep AI crawlers out. So I asked two questions that have exact answers:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Can a model read a robots.txt?&lt;/strong&gt; Given the file, a crawler name and a URL, is the fetch allowed?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Can a model write one?&lt;/strong&gt; Given a plain-English policy ("keep GPTBot out of /blog/, keep everyone out of /private/ except /private/press/"), does the file it writes actually do that?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Robots.txt has a real standard, &lt;a href="https://www.rfc-editor.org/rfc/rfc9309" rel="noopener noreferrer"&gt;RFC 9309&lt;/a&gt; (2022), so neither question needs a judge model. Every answer is checked by a reference parser.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The three tasks&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Task&lt;/th&gt;
&lt;th&gt;What the model gets&lt;/th&gt;
&lt;th&gt;Cases&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Read: rules&lt;/td&gt;
&lt;td&gt;a small file testing one rule of the standard (10 categories: longest match, ties, &lt;code&gt;*&lt;/code&gt;, &lt;code&gt;$&lt;/code&gt;, the crawler's own group, merged groups, path case, …)&lt;/td&gt;
&lt;td&gt;240&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Read: real sites&lt;/td&gt;
&lt;td&gt;the real robots.txt of 41 public sites (Wikipedia, GitHub, Amazon, NYT, Etsy, …), with URLs placed on the edges of their rules&lt;/td&gt;
&lt;td&gt;240&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Write: policy&lt;/td&gt;
&lt;td&gt;a 3–6 line policy. The file it writes is then probed with every crawler × URL the policy covers&lt;/td&gt;
&lt;td&gt;40 policies, 1,836 probes&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;How the answers are checked.&lt;/strong&gt; My RFC 9309 matcher agrees with &lt;a href="https://github.com/scrapy/protego" rel="noopener noreferrer"&gt;Protego&lt;/a&gt; (Scrapy's parser) on all 480 reading cases. For each policy I also generated a reference file that passes every probe, so each policy can be done. A written file passes only if &lt;em&gt;every&lt;/em&gt; probe is right, because one wrong rule in a real robots.txt lets a crawler in.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A baseline that surprised me:&lt;/strong&gt; Python's own &lt;code&gt;urllib.robotparser&lt;/code&gt; gets only &lt;strong&gt;70.4%&lt;/strong&gt; of the rules cases and &lt;strong&gt;73.3%&lt;/strong&gt; of the real-site cases right. It lets the first matching rule win (the standard says the longest), and it doesn't understand &lt;code&gt;*&lt;/code&gt; or &lt;code&gt;$&lt;/code&gt;. If your scraper uses the standard library, it is misreading about one robots.txt question in four.&lt;/p&gt;

&lt;h2&gt;
  
  
  Models Tested
&lt;/h2&gt;

&lt;p&gt;Nine models from five families, small and large, all run through Kaggle's Model Proxy (Kaggle pays for the calls):&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Family&lt;/th&gt;
&lt;th&gt;Models&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Google&lt;/td&gt;
&lt;td&gt;Gemini 3.7 Flash, Gemini 3.5 Flash-Lite, Gemini 3.1 Pro (preview), Gemma 4 31B (open weights)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Anthropic&lt;/td&gt;
&lt;td&gt;Claude Sonnet 5.5, Claude Haiku 4.5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OpenAI&lt;/td&gt;
&lt;td&gt;GPT-6.1 Sol, GPT-5.4 nano&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Alibaba&lt;/td&gt;
&lt;td&gt;Qwen3-Next 80B-A3B (open weights)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Models I tried and left out, and why.&lt;/strong&gt; I would rather say it than hide it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;Claude Opus 5.5:&lt;/em&gt; too expensive for Kaggle's $10-a-day model quota alongside everything else.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;DeepSeek-R1:&lt;/em&gt; the daily quota ran out mid-run (184 of 240 rules cases and 159 of 240 real-site cases done). An unfinished run is not a score, so it is excluded, not shown as a low one.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Qwen3-235B and gpt-oss-120b:&lt;/em&gt; through the proxy they would not answer even a one-word test prompt within 100 seconds, so their runs timed out with almost nothing done. That looks like a proxy-side problem, not a model one.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Grok 4.6:&lt;/em&gt; no quota or time left before the deadline.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each model ran each task once at the proxy's default settings. Writing scores move between runs: the same model (Gemini 3.7 Flash) wrote 24, then 27, then 22 of 40 policies fully right on three runs of the benchmark, so treat gaps of a few policies as noise. The gaps in the table below between the top and bottom are far larger than that.&lt;/p&gt;

&lt;h2&gt;
  
  
  Findings
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;th&gt;Read: rules&lt;/th&gt;
&lt;th&gt;Read: real sites&lt;/th&gt;
&lt;th&gt;Write: policies fully right&lt;/th&gt;
&lt;th&gt;Write: probes right&lt;/th&gt;
&lt;th&gt;Probes that let a blocked crawler in&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Claude Sonnet 5.5&lt;/td&gt;
&lt;td&gt;0.988&lt;/td&gt;
&lt;td&gt;1.000&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;40 / 40&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;100.0%&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Gemini 3.1 Pro&lt;/td&gt;
&lt;td&gt;0.996&lt;/td&gt;
&lt;td&gt;1.000&lt;/td&gt;
&lt;td&gt;28 / 40&lt;/td&gt;
&lt;td&gt;92.3%&lt;/td&gt;
&lt;td&gt;110&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Gemini 3.7 Flash&lt;/td&gt;
&lt;td&gt;1.000&lt;/td&gt;
&lt;td&gt;1.000&lt;/td&gt;
&lt;td&gt;22 / 40&lt;/td&gt;
&lt;td&gt;97.5%&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GPT-6.1 Sol&lt;/td&gt;
&lt;td&gt;1.000&lt;/td&gt;
&lt;td&gt;0.971&lt;/td&gt;
&lt;td&gt;17 / 40&lt;/td&gt;
&lt;td&gt;69.9%&lt;/td&gt;
&lt;td&gt;552&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Gemma 4 31B&lt;/td&gt;
&lt;td&gt;0.967&lt;/td&gt;
&lt;td&gt;0.992&lt;/td&gt;
&lt;td&gt;14 / 40&lt;/td&gt;
&lt;td&gt;92.3%&lt;/td&gt;
&lt;td&gt;78&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Gemini 3.5 Flash-Lite&lt;/td&gt;
&lt;td&gt;0.929&lt;/td&gt;
&lt;td&gt;0.921&lt;/td&gt;
&lt;td&gt;15 / 40&lt;/td&gt;
&lt;td&gt;95.3%&lt;/td&gt;
&lt;td&gt;74&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Claude Haiku 4.5&lt;/td&gt;
&lt;td&gt;0.871&lt;/td&gt;
&lt;td&gt;0.925&lt;/td&gt;
&lt;td&gt;10 / 40&lt;/td&gt;
&lt;td&gt;90.1%&lt;/td&gt;
&lt;td&gt;125&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Qwen3-Next 80B&lt;/td&gt;
&lt;td&gt;0.854&lt;/td&gt;
&lt;td&gt;0.896&lt;/td&gt;
&lt;td&gt;5 / 40&lt;/td&gt;
&lt;td&gt;76.4%&lt;/td&gt;
&lt;td&gt;372&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GPT-5.4 nano&lt;/td&gt;
&lt;td&gt;0.792&lt;/td&gt;
&lt;td&gt;0.787&lt;/td&gt;
&lt;td&gt;5 / 40&lt;/td&gt;
&lt;td&gt;77.6%&lt;/td&gt;
&lt;td&gt;371&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;(Gemini 3.1 Pro: one case in each of two tasks failed on Kaggle's side; both runs are above the 95%-completed bar I set before scoring, and the missing case counts as wrong.)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Reading is nearly solved, and the hard cases are the ones the standard library gets wrong.&lt;/strong&gt; Six of nine models read the 240 single-rule files at 92% or better, and four score 99% or better on the real sites. The errors that remain cluster in wildcard rules (17% of answers wrong across models), prefix rules (12%) and &lt;code&gt;$&lt;/code&gt; end anchors (11%). On the real sites, the "near miss" questions, where a URL sits right on the edge of a rule, are wrong 8.8% of the time, against 3.4% for plain ones. The longest-match rule, the one Python's &lt;code&gt;urllib.robotparser&lt;/code&gt; gets wrong, was answered correctly in all 216 attempts.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Writing is much harder, and "almost right" is the failure to watch.&lt;/strong&gt; Across 360 attempts (9 models × 40 policies), only 156 files did everything the policy asked. Most models still get 90%+ of individual probes right, which is exactly why this is risky: a file that is 95% correct reads as correct. The worst leak: GPT-6.1 Sol read the rules perfectly (1.000), yet 552 of its probes let a crawler in that the policy blocks, with zero over-blocking. It writes files that are too permissive. The best writer, Claude Sonnet 5.5, passed all 40 policies with no leaks and no over-blocks.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Parameters and file extensions are where files break.&lt;/strong&gt; Policies with a query-parameter rule (such as "block any URL with &lt;code&gt;?token=&lt;/code&gt;") were fully right 25% of the time, against 66% for policies without one. Policies with a file-extension rule ("never serve &lt;code&gt;.pdf&lt;/code&gt;") were fully right 23% of the time, against 57% without. The most common slip: &lt;code&gt;Disallow: /*.pdf$&lt;/code&gt; blocks &lt;code&gt;/report.pdf&lt;/code&gt; but not &lt;code&gt;/report.pdf?dl=1&lt;/code&gt;, because &lt;code&gt;$&lt;/code&gt; anchors the end of the whole URL including the query string (88 leaked probes). Others: writing &lt;code&gt;Disallow: /drafts/$&lt;/code&gt; when the policy says "anything under /drafts/", which blocks only the folder's index page; and a parameter rule written without the &lt;code&gt;=&lt;/code&gt;, so &lt;code&gt;?tokenx=1&lt;/code&gt; gets blocked too (231 over-blocked probes). A crawler that has its own &lt;code&gt;User-agent&lt;/code&gt; group stops obeying the &lt;code&gt;*&lt;/code&gt; group, so a "no crawler may fetch /drafts/" rule has to be repeated in every named group. That second-largest leak cause (588 probes) is the trap I expected, and the models do fall into it, but it is not the biggest.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What I'd measure next:&lt;/strong&gt; agents that fetch robots.txt themselves instead of being handed it, &lt;code&gt;Crawl-delay&lt;/code&gt; and &lt;code&gt;Sitemap&lt;/code&gt; lines, non-ASCII paths, and the same writing task with the RFC pasted in the prompt, to see whether the standard is the gap or the reasoning is.&lt;/p&gt;

&lt;h2&gt;
  
  
  My Benchmark
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Benchmark: &lt;a href="https://www.kaggle.com/benchmarks/oldjard/robots-txt-benchmark-can-llms-read-and-write-robo" rel="noopener noreferrer"&gt;https://www.kaggle.com/benchmarks/oldjard/robots-txt-benchmark-can-llms-read-and-write-robo&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Dataset (cases, the 41 robots.txt files as fetched on 2026-10-05, the checker code): &lt;a href="https://www.kaggle.com/datasets/oldjard/robots-txt-benchmark" rel="noopener noreferrer"&gt;https://www.kaggle.com/datasets/oldjard/robots-txt-benchmark&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Everything is seeded and rebuildable. The reference parser is about 100 lines and the probe checker about 150.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Written with help from an AI agent (see the note at the top). Comments and replies are mine.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>kagglechallenge</category>
      <category>ai</category>
      <category>webdev</category>
      <category>benchmark</category>
    </item>
  </channel>
</rss>
