<?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: Daniel Nwaneri</title>
    <description>The latest articles on DEV Community by Daniel Nwaneri (@dannwaneri).</description>
    <link>https://dev.to/dannwaneri</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%2F3606168%2F7684e1e1-b986-4ee3-ae5b-56db2b97d286.jpg</url>
      <title>DEV Community: Daniel Nwaneri</title>
      <link>https://dev.to/dannwaneri</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/dannwaneri"/>
    <language>en</language>
    <item>
      <title>I Told a PR Nothing Would Break. Qodo Checked.</title>
      <dc:creator>Daniel Nwaneri</dc:creator>
      <pubDate>Tue, 22 Sep 2026 11:51:43 +0000</pubDate>
      <link>https://dev.to/dannwaneri/i-told-a-pr-nothing-would-break-qodo-checked-32nd</link>
      <guid>https://dev.to/dannwaneri/i-told-a-pr-nothing-would-break-qodo-checked-32nd</guid>
      <description>&lt;p&gt;Every PR description is a claim. "No consumers of this API were changed." "Safe to merge." Most review tools take that claim at face value, because checking it would mean looking outside the diff.&lt;/p&gt;

&lt;p&gt;I wanted to know what happens when a review tool doesn't take the claim at face value. So I built two connected services. I made a change that looked completely safe in isolation. I wrote a PR description that explicitly said nothing downstream would break. Then I let &lt;a href="https://qodo.ai" rel="noopener noreferrer"&gt;Qodo&lt;/a&gt; decide whether to believe me.&lt;/p&gt;

&lt;p&gt;It didn't.&lt;/p&gt;




&lt;h2&gt;
  
  
  the setup
&lt;/h2&gt;

&lt;p&gt;Two small repos, deliberately simple:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/dannwaneri/orders-api" rel="noopener noreferrer"&gt;&lt;code&gt;orders-api&lt;/code&gt;&lt;/a&gt; — a Cloudflare Worker. &lt;code&gt;GET /orders/:id&lt;/code&gt; returns &lt;code&gt;{ id, status, total, currency }&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/dannwaneri/orders-client" rel="noopener noreferrer"&gt;&lt;code&gt;orders-client&lt;/code&gt;&lt;/a&gt; — a consumer that fetches an order and fulfills it if &lt;code&gt;order.status === 'paid'&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Baseline works end to end. Fetch a paid order, it fulfills. Fetch a pending order, it skips. Nothing exotic.&lt;/p&gt;

&lt;p&gt;Then I opened a PR on &lt;code&gt;orders-api&lt;/code&gt; that changes &lt;code&gt;status&lt;/code&gt; from a plain string to a nested object: &lt;code&gt;{ value, updatedAt }&lt;/code&gt;. The reasoning in the PR description was real: I wanted an audit trail for when an order's status last changed. The description also said this explicitly:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"No consumers of this API were changed as part of this PR — response shape update only."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That sentence is true and misleading at the same time. No consumer's &lt;em&gt;code&lt;/em&gt; changed. But &lt;code&gt;orders-client&lt;/code&gt; still does &lt;code&gt;order.status === 'paid'&lt;/code&gt;, and that comparison (a string against an object) silently returns &lt;code&gt;false&lt;/code&gt; forever. Paid orders stop being fulfilled. No crash, no error, no log line that looks wrong. Just orders that quietly never ship.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;orders-api&lt;/code&gt;'s own type checker sees none of this. Nothing in that repo references &lt;code&gt;orders-client&lt;/code&gt;. The break is real. It's completely invisible from inside the repo where it originates.&lt;/p&gt;




&lt;h2&gt;
  
  
  running qodo
&lt;/h2&gt;

&lt;p&gt;I commented &lt;code&gt;/agentic_review&lt;/code&gt; on the PR. Ninety seconds later:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Action required: Existing clients misread order statuses&lt;/strong&gt; 🐞 Bug · Correctness&lt;/p&gt;

&lt;p&gt;"Order.status changes from the existing string union to an OrderStatus object while the same route continues serializing the order without versioning or negotiation. Every successful order request now returns an incompatible field to existing consumers, including the documented orders client that this PR explicitly leaves unchanged."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That last clause is quoting my own PR description back at me, the part where I said nothing downstream was affected, and pointing out that it's wrong. Qodo didn't guess this. It checked &lt;code&gt;orders-client&lt;/code&gt;, the actual repo, and found the line that breaks.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://youtu.be/xSrXgXC-Qig" rel="noopener noreferrer"&gt;https://youtu.be/xSrXgXC-Qig&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  proving it wasn't a lucky guess
&lt;/h2&gt;

&lt;p&gt;One finding could just mean Qodo read my PR description carefully and got suspicious of the wording, not that it checked the other repo at all. I wanted to rule that out.&lt;/p&gt;

&lt;p&gt;So I ran a control. Same exact code change. Same PR description, word for word. The only variable: I removed &lt;code&gt;orders-client&lt;/code&gt; from Qodo's connected repositories first.&lt;/p&gt;

&lt;p&gt;The result, on an otherwise identical PR:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Great, no issues found!&lt;/strong&gt;&lt;br&gt;
"Qodo reviewed your code and found no material issues that require review."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Connected: one action-required bug, correctly named, correctly explained. Disconnected: a clean pass. Same code, same description, same claim about consumers being unaffected. The only thing that changed was whether Qodo could see the repo the claim was about.&lt;/p&gt;

&lt;p&gt;That rules out coincidence. The connection is causal.&lt;/p&gt;




&lt;h2&gt;
  
  
  what surprised me
&lt;/h2&gt;

&lt;p&gt;The "no issues found" screen is the one that stuck with me, not the bug it missed.&lt;/p&gt;

&lt;p&gt;It's polished. Little Qodo mascot, clean green checkmark. If you saw that screen in isolation, it reads like an endorsement: ship it, you're clear. It isn't lying, exactly. Qodo found no issues &lt;em&gt;in what it could see&lt;/em&gt;. But "no issues found" and "no issues exist" are different claims, and the UI doesn't visually distinguish between them.&lt;/p&gt;

&lt;p&gt;Every review tool has this problem, not just Qodo: a clean bill of health only means as much as the tool's field of view. A confident, cheerful "all clear" from a tool that can't see half the picture is more dangerous than an honest "I don't know," because nothing about the interaction tells you to doubt it.&lt;/p&gt;

&lt;p&gt;The fix is exactly what connecting &lt;code&gt;orders-client&lt;/code&gt; did: widen what the tool can see before it renders a verdict at all.&lt;/p&gt;




&lt;h2&gt;
  
  
  single-repo vs. connected
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;orders-api&lt;/code&gt;'s own review pipeline (tests, types, linting) has no way to catch this. It was never going to. The bug doesn't live in &lt;code&gt;orders-api&lt;/code&gt;. It lives in the gap between two repos that don't know about each other.&lt;/p&gt;

&lt;p&gt;This is why cross-repo context matters here: it catches a false claim of safety that a single repo has no way to verify. Most bugs in most PRs are contained to the diff. This one specifically wasn't, and the failure mode when you get it wrong is the quiet kind: nothing breaks loudly, revenue just doesn't fulfill, and someone eventually finds out from a support ticket, not a stack trace.&lt;/p&gt;

&lt;p&gt;Single-repo tools aren't wrong to focus on the diff. They're just answering a narrower question than the one that mattered here.&lt;/p&gt;




&lt;h2&gt;
  
  
  takeaway
&lt;/h2&gt;

&lt;p&gt;Your PR description is a claim, not a fact. "Nothing downstream changed" is something you believe until the thing checking your PR can actually see downstream.&lt;/p&gt;

&lt;p&gt;I wrote a true sentence that was still wrong. Qodo caught the wrong part. Then I proved it wasn't luck by taking the same claim, removing the tool's ability to check it, and watching it get fooled by the exact same sentence.&lt;/p&gt;

&lt;p&gt;If your review tool can't see the repos your change touches, it isn't reviewing your change. It's reviewing your diff and trusting your description for everything else.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Repos: &lt;a href="https://github.com/dannwaneri/orders-api" rel="noopener noreferrer"&gt;orders-api&lt;/a&gt; · &lt;a href="https://github.com/dannwaneri/orders-client" rel="noopener noreferrer"&gt;orders-client&lt;/a&gt;. Qodo runs on the free tier for public repos — &lt;a href="https://qodo.ai" rel="noopener noreferrer"&gt;qodo.ai&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>security</category>
      <category>discuss</category>
      <category>webdev</category>
    </item>
    <item>
      <title>My AI Agent Isn't Allowed to Decide Anything</title>
      <dc:creator>Daniel Nwaneri</dc:creator>
      <pubDate>Mon, 21 Sep 2026 12:35:25 +0000</pubDate>
      <link>https://dev.to/dannwaneri/my-ai-agent-isnt-allowed-to-decide-anything-2fe2</link>
      <guid>https://dev.to/dannwaneri/my-ai-agent-isnt-allowed-to-decide-anything-2fe2</guid>
      <description>&lt;p&gt;It's a few hours before a delivery deadline and the work has piled up. Somewhere in that pile is a piece of work that's about to miss its deadline, unless someone pushes a different piece back to make room. That's the exact moment I built this for.&lt;/p&gt;

&lt;p&gt;Three out of five. That's how often Shot-Delivery Guardian, the system I built for Google's Agentic Cinema hackathon, picks the right answer when it's the one making that call, in real tests against the actual running system, not just on my laptop. But here's the number that matters more: zero broken rules. It never once picked a piece of work the client had already signed off on. It never once picked something other work was waiting on. It never once touched anything the director had flagged as important.&lt;/p&gt;

&lt;p&gt;The AI never got an important decision wrong, because I never let it make one.&lt;/p&gt;

&lt;p&gt;Picture a movie or TV show going through its last steps: editing, effects, color, quality checks, then handoff to the streaming service. If one of those steps backs up, something is now going to miss its deadline unless a person delays something else to make room. That choice has to happen fast, and it can't touch anything the client already approved, can't hold up something else that's waiting on it, can't be the one thing the director cared about most. Right now, a person does this by hand, checking a bunch of screens at once, usually in a hurry.&lt;/p&gt;

&lt;p&gt;So I built a system that watches the work as it moves through each step, notices when a deadline is about to be missed, figures out why, and says which piece of work is safe to push back.&lt;/p&gt;

&lt;p&gt;Here's the part I care about most: an AI can be great at figuring out what's going on. It shouldn't be the thing that makes the final call on something with real money or a real contract riding on it.&lt;/p&gt;

&lt;p&gt;It's tempting to just hand the AI everything: here's what's happening, tell me what to delay, go. I didn't do that. The actual choice comes from a short, fixed list of rules, written as plain code, not from the AI's own judgment:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;score_bump_candidates&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;shots&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;eligible&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;shots&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;client_approved&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;director_flagged&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="nf"&gt;has_downstream_dependents&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;shots&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;eligible&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;slack_hours&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;reverse&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In plain words: it throws out anything the client already signed off on, anything the director cared about, and anything other work is waiting on, then picks whichever of what's left has the most spare time before its own deadline. The AI runs this rule and reports the answer back, word for word. It doesn't get to argue with it or dress it up to sound more sure of itself than it is.&lt;/p&gt;

&lt;p&gt;I split the system into eight small pieces, one each for intake, rendering, color, quality checks, delivery, scheduling, a test generator, and the AI itself, instead of one giant program, so a slowdown in one place can't take down everything else with it. Between them sits something like a waiting line, because work moves at different speeds in different places, and a slow spot shouldn't block everything behind it.&lt;/p&gt;

&lt;p&gt;Every one of those eight pieces reports what it's doing the same way, so I didn't have to build a different reporting system for every tool watching it.&lt;/p&gt;

&lt;p&gt;I used three different kinds of monitoring, because one wasn't enough. One tells me how bad the backlog is overall. Another tells me which specific pieces of work are involved and what I know about them, like who approved it and when it's due. A third tells me exactly where, inside one piece of work's journey, the time got lost. No single one answers the whole question.&lt;/p&gt;

&lt;p&gt;To let the AI ask all three of these tools questions, and write its answer back onto the same dashboard a person already watches, I used one shared connector instead of building three separate ones by hand. It also logs in on its own, with no person needed to click anything, so the whole thing can kick off automatically the moment something looks wrong.&lt;/p&gt;

&lt;p&gt;The AI is doing the part it's actually good at. If it's not making the final call, why have it at all? Turning "the queue is packed and barely clearing" into something a person can act on in five seconds, that's explaining, not deciding. It's also doing the legwork: check the first tool, then the second, then the third, run the rule, write it up. A plain script could do that same sequence in order. It just couldn't explain itself in plain English while doing it.&lt;/p&gt;

&lt;p&gt;The answer shows up as a note right on the dashboard the person on call is already looking at. No new place to check.&lt;/p&gt;

&lt;p&gt;The cleanest demo isn't always the most convincing one. During one real test, the system hit a real problem reaching one of its three tools halfway through. It said so, plainly, in its own words. It didn't hide the gap, and it didn't guess. It used what it could still get from the other tools, and still gave one clear answer: this exact piece of work, with about an hour of spare time before its deadline, safe to push back.&lt;/p&gt;

&lt;p&gt;That's a better proof than a clean run would've been. The fixed rule didn't need the AI to get everything right. It just needed whatever real information made it through.&lt;/p&gt;

&lt;p&gt;So, the honest numbers: 3 of 5 real tests matched what the fixed rule alone would've picked. About 44 seconds on average to look into a problem and respond, timed against the real live system. Zero broken rules across every single test.&lt;/p&gt;

&lt;p&gt;60% isn't a number I'd normally want to lead with. But it's real, it came from a live system doing real work, and I didn't round it up. I'd rather show an honest 60% than a demo that only ever shows the one time it worked.&lt;/p&gt;

&lt;p&gt;Let the AI look into things, explain them, and handle the steps. Keep the piece that actually decides, the one touching money or contracts, as a fixed rule the AI can't talk its way around. It doesn't need to be trusted with the decision to be useful. It just needs to be trusted to explain one.&lt;/p&gt;

&lt;p&gt;Code's at github.com/dannwaneri/shot-delivery-guardian, MIT licensed.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>showdev</category>
      <category>googlecloud</category>
      <category>gemini</category>
    </item>
    <item>
      <title>ngl, "asked an agent to find where my space went and the agent was the problem" is the most 2026 sentence I've read this week.</title>
      <dc:creator>Daniel Nwaneri</dc:creator>
      <pubDate>Fri, 18 Sep 2026 22:36:37 +0000</pubDate>
      <link>https://dev.to/dannwaneri/ngl-asked-an-agent-to-find-where-my-space-went-and-the-agent-was-the-problem-is-the-most-2026-3nh3</link>
      <guid>https://dev.to/dannwaneri/ngl-asked-an-agent-to-find-where-my-space-went-and-the-agent-was-the-problem-is-the-most-2026-3nh3</guid>
      <description></description>
      <category>agents</category>
      <category>ai</category>
      <category>llm</category>
    </item>
    <item>
      <title>[Boost]</title>
      <dc:creator>Daniel Nwaneri</dc:creator>
      <pubDate>Fri, 18 Sep 2026 08:45:13 +0000</pubDate>
      <link>https://dev.to/dannwaneri/-2phg</link>
      <guid>https://dev.to/dannwaneri/-2phg</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/dannwaneri/1-in-10-sites-have-a-broken-llmstxt-reddit-included-50ln" class="crayons-story__hidden-navigation-link"&gt;1 in 10 Sites Have a Broken llms.txt. Reddit Included.&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/dannwaneri" class="crayons-avatar  crayons-avatar--l  "&gt;
            &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3606168%2F7684e1e1-b986-4ee3-ae5b-56db2b97d286.jpg" alt="dannwaneri profile" class="crayons-avatar__image"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/dannwaneri" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Daniel Nwaneri
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Daniel Nwaneri
                &lt;a href="/++"&gt;&lt;img alt="Subscriber" class="subscription-icon" src="https://assets.dev.to/assets/subscription-icon-805dfa7ac7dd660f07ed8d654877270825b07a92a03841aa99a1093bd00431b2.png"&gt;&lt;/a&gt;
                
              
              &lt;div id="story-author-preview-content-4648847" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/dannwaneri" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&gt;
                        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3606168%2F7684e1e1-b986-4ee3-ae5b-56db2b97d286.jpg" class="crayons-avatar__image" alt=""&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Daniel Nwaneri&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/dannwaneri/1-in-10-sites-have-a-broken-llmstxt-reddit-included-50ln" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Sep 15&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/dannwaneri/1-in-10-sites-have-a-broken-llmstxt-reddit-included-50ln" id="article-link-4648847"&gt;
          1 in 10 Sites Have a Broken llms.txt. Reddit Included.
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/ai"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;ai&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/seo"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;seo&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/python"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;python&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/opensource"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;opensource&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/dannwaneri/1-in-10-sites-have-a-broken-llmstxt-reddit-included-50ln" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/multi-unicorn-b44d6f8c23cdd00964192bedc38af3e82463978aa611b4365bd33a0f1f4f3e97.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/fire-f60e7a582391810302117f987b22a8ef04a2fe0df7e3258a5f49332df1cec71e.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;17&lt;span class="hidden s:inline"&gt;&amp;nbsp;reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;span class="crayons-story__favorited"&gt;
              &lt;span class="favorited-marker"&gt;
                &lt;span&gt;
                  

                &lt;/span&gt;
                &lt;span class="hidden"&gt;
                  

                &lt;/span&gt;
              &lt;/span&gt;
            &lt;/span&gt;
            &lt;a href="https://dev.to/dannwaneri/1-in-10-sites-have-a-broken-llmstxt-reddit-included-50ln#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

              5&lt;span class="hidden s:inline"&gt;&amp;nbsp;comments&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            5 min read
          &lt;/small&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


</description>
    </item>
    <item>
      <title>[Boost]</title>
      <dc:creator>Daniel Nwaneri</dc:creator>
      <pubDate>Thu, 17 Sep 2026 16:05:13 +0000</pubDate>
      <link>https://dev.to/dannwaneri/-21hf</link>
      <guid>https://dev.to/dannwaneri/-21hf</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/dannwaneri/ai-made-hackathons-easier-to-finish-thats-the-problem-32ck" class="crayons-story__hidden-navigation-link"&gt;AI Made Hackathons Easier to Finish. That's the Problem.&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/dannwaneri" class="crayons-avatar  crayons-avatar--l  "&gt;
            &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3606168%2F7684e1e1-b986-4ee3-ae5b-56db2b97d286.jpg" alt="dannwaneri profile" class="crayons-avatar__image" width="800" height="1062"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/dannwaneri" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Daniel Nwaneri
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Daniel Nwaneri
                &lt;a href="/++"&gt;&lt;img alt="Subscriber" class="subscription-icon" src="https://assets.dev.to/assets/subscription-icon-805dfa7ac7dd660f07ed8d654877270825b07a92a03841aa99a1093bd00431b2.png" width="166" height="102"&gt;&lt;/a&gt;
                
              
              &lt;div id="story-author-preview-content-4651006" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/dannwaneri" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&gt;
                        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3606168%2F7684e1e1-b986-4ee3-ae5b-56db2b97d286.jpg" class="crayons-avatar__image" alt="" width="800" height="1062"&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Daniel Nwaneri&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/dannwaneri/ai-made-hackathons-easier-to-finish-thats-the-problem-32ck" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Sep 14&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/dannwaneri/ai-made-hackathons-easier-to-finish-thats-the-problem-32ck" id="article-link-4651006"&gt;
          AI Made Hackathons Easier to Finish. That's the Problem.
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag crayons-tag--filled  " href="/t/discuss"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;discuss&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/ai"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;ai&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/hackathon"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;hackathon&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/productivity"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;productivity&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/dannwaneri/ai-made-hackathons-easier-to-finish-thats-the-problem-32ck" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/multi-unicorn-b44d6f8c23cdd00964192bedc38af3e82463978aa611b4365bd33a0f1f4f3e97.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/raised-hands-74b2099fd66a39f2d7eed9305ee0f4553df0eb7b4f11b01b6b1b499973048fe5.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;20&lt;span class="hidden s:inline"&gt;&amp;nbsp;reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/dannwaneri/ai-made-hackathons-easier-to-finish-thats-the-problem-32ck#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

              8&lt;span class="hidden s:inline"&gt;&amp;nbsp;comments&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            3 min read
          &lt;/small&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


</description>
    </item>
    <item>
      <title>[Boost]</title>
      <dc:creator>Daniel Nwaneri</dc:creator>
      <pubDate>Thu, 17 Sep 2026 07:01:35 +0000</pubDate>
      <link>https://dev.to/dannwaneri/-2mmo</link>
      <guid>https://dev.to/dannwaneri/-2mmo</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/dannwaneri/1-in-10-sites-have-a-broken-llmstxt-reddit-included-50ln" class="crayons-story__hidden-navigation-link"&gt;1 in 10 Sites Have a Broken llms.txt. Reddit Included.&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/dannwaneri" class="crayons-avatar  crayons-avatar--l  "&gt;
            &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3606168%2F7684e1e1-b986-4ee3-ae5b-56db2b97d286.jpg" alt="dannwaneri profile" class="crayons-avatar__image"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/dannwaneri" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Daniel Nwaneri
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Daniel Nwaneri
                &lt;a href="/++"&gt;&lt;img alt="Subscriber" class="subscription-icon" src="https://assets.dev.to/assets/subscription-icon-805dfa7ac7dd660f07ed8d654877270825b07a92a03841aa99a1093bd00431b2.png"&gt;&lt;/a&gt;
                
              
              &lt;div id="story-author-preview-content-4648847" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/dannwaneri" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&gt;
                        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3606168%2F7684e1e1-b986-4ee3-ae5b-56db2b97d286.jpg" class="crayons-avatar__image" alt=""&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Daniel Nwaneri&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/dannwaneri/1-in-10-sites-have-a-broken-llmstxt-reddit-included-50ln" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Sep 15&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/dannwaneri/1-in-10-sites-have-a-broken-llmstxt-reddit-included-50ln" id="article-link-4648847"&gt;
          1 in 10 Sites Have a Broken llms.txt. Reddit Included.
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/ai"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;ai&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/seo"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;seo&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/python"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;python&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/opensource"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;opensource&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/dannwaneri/1-in-10-sites-have-a-broken-llmstxt-reddit-included-50ln" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/multi-unicorn-b44d6f8c23cdd00964192bedc38af3e82463978aa611b4365bd33a0f1f4f3e97.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/fire-f60e7a582391810302117f987b22a8ef04a2fe0df7e3258a5f49332df1cec71e.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;17&lt;span class="hidden s:inline"&gt;&amp;nbsp;reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;span class="crayons-story__favorited"&gt;
              &lt;span class="favorited-marker"&gt;
                &lt;span&gt;
                  

                &lt;/span&gt;
                &lt;span class="hidden"&gt;
                  

                &lt;/span&gt;
              &lt;/span&gt;
            &lt;/span&gt;
            &lt;a href="https://dev.to/dannwaneri/1-in-10-sites-have-a-broken-llmstxt-reddit-included-50ln#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

              5&lt;span class="hidden s:inline"&gt;&amp;nbsp;comments&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            5 min read
          &lt;/small&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


</description>
    </item>
    <item>
      <title>1 in 10 Sites Have a Broken llms.txt. Reddit Included.</title>
      <dc:creator>Daniel Nwaneri</dc:creator>
      <pubDate>Tue, 15 Sep 2026 14:03:28 +0000</pubDate>
      <link>https://dev.to/dannwaneri/1-in-10-sites-have-a-broken-llmstxt-reddit-included-50ln</link>
      <guid>https://dev.to/dannwaneri/1-in-10-sites-have-a-broken-llmstxt-reddit-included-50ln</guid>
      <description>&lt;p&gt;Quick recap if you're new here: last month I tested whether five different AI engines (Claude, ChatGPT, Gemini, Perplexity, Bing Copilot) would cite my own websites when answering people's questions. Mostly, they didn't. I chalked some of that up to normal stuff, small sites, not enough content out there about them yet.&lt;/p&gt;

&lt;p&gt;Then a reader named &lt;a href="https://dev.to/rileycraig14"&gt;riley craig&lt;/a&gt; left a comment that changed the story.&lt;/p&gt;

&lt;p&gt;He'd checked two files on my sites that I hadn't thought to check myself: robots.txt and llms.txt. Both are supposed to be small text files that live at the root of a website (yoursite.com/robots.txt) and tell automated visitors, like search engines and AI crawlers, what they're allowed to look at and where to find the important stuff. robots.txt has been standard since the 1990s. llms.txt is a newer, similar idea, specifically aimed at AI tools, and it's meant to be a plain, boring text file, nothing fancy.&lt;/p&gt;

&lt;p&gt;Mine weren't text files. They were my homepage.&lt;/p&gt;

&lt;p&gt;Every time anything, human or bot, requested robots.txt or llms.txt on my site, my server handed back the exact same 35KB webpage you'd get by just visiting the homepage, titled "Remote AI Agent Developer." My other site, naija-vpn.com, had the same problem on llms.txt (its robots.txt was fine, that one worked correctly).&lt;/p&gt;

&lt;p&gt;Modern websites are often built as what's called a single-page app, or SPA. One HTML page loads, and then JavaScript swaps content in and out as you click around, instead of loading a fresh page every time. To make that work, the hosting platform (mine is Cloudflare Pages) is usually set up to hand you the main app page whenever you ask for a page that doesn't exist as a real file. The assumption is that you're a person who clicked a broken link and should land somewhere useful.&lt;/p&gt;

&lt;p&gt;That fallback is helpful for people. It's a problem for robots.txt and llms.txt, because those specifically need to be small, plain text files, not a webpage. My site's build process, the step that turns source code into the files that get uploaded and served, never generated those two files at all. So every request for them just fell through to the homepage fallback. An AI crawler asking "what am I allowed to read here" got back a full HTML page instead of a straight answer.&lt;/p&gt;

&lt;p&gt;I fixed both sites. Confirmed the fix works by checking the files myself afterward. That part took an afternoon.&lt;/p&gt;

&lt;p&gt;Fixing my own two small sites isn't much of a story on its own. Anyone could have that exact same bug and never notice, because nothing about it throws an error. Your site still loads fine for anyone visiting it normally. It just quietly gives bots the wrong answer.&lt;/p&gt;

&lt;p&gt;So I built a small tool to check for this automatically: &lt;code&gt;ai_crawlability.py&lt;/code&gt;, a new piece of an open-source SEO toolkit I maintain. Point it at a domain and it requests robots.txt, llms.txt, and sitemap.xml (a third standard file, this one lists all the pages on a site so search engines can find them), then checks whether each one comes back as a real text file or as a webpage in disguise.&lt;/p&gt;

&lt;p&gt;If a site simply doesn't have an llms.txt file at all, that's not this bug. Most sites don't have one yet since it's a new convention, and a normal "file not found" response is completely different from what happened to me. The bug I'm looking for is this: the server says "yes, here's your file" (a status code of 200, which just means "success, request completed normally") but hands you a full webpage instead of the plain text you asked for.&lt;/p&gt;

&lt;p&gt;The tool can check specific sites you give it directly. It can also do something more interesting: pull a real, unbiased sample of domains straight from Google search results (using SearchApi, the search API company sponsoring this series of articles) for whatever search terms you give it, then check every domain in that sample. That second mode is the point. Checking my own two sites tells you nothing about how common this bug really is. Checking a real sample does.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a real sample turned up
&lt;/h2&gt;

&lt;p&gt;I picked ten completely unrelated search topics on purpose, developer tools, travel booking, productivity software, laptop reviews, coffee subscriptions, specifically so the sample wouldn't just turn into a pile of other companies similar to mine. SearchApi's search results gave me 50 unique domains across all ten searches. I checked every one.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Result&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;Working correctly&lt;/td&gt;
&lt;td&gt;39&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Has the bug (webpage instead of text file)&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Blocked the request or errored out (a different, unrelated problem)&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Five broken out of fifty checked. That's one in ten.&lt;/p&gt;

&lt;p&gt;reddit.com has the bug on both llms.txt and sitemap.xml. So does skyscanner.com. paymoapp.com has it on llms.txt. And two different Medium.com blogs, run by two different people (ilampadmanabhan.medium.com and navanathjadhav.medium.com), both have it on sitemap.xml, with the broken webpage they each got back weighing in at 41,975 and 41,972 bytes respectively.&lt;/p&gt;

&lt;p&gt;Two unrelated writers didn't independently make the identical mistake. Those numbers being that close means it's almost certainly Medium's own platform serving the same broken response to everyone who points a custom domain at a Medium blog. If that's true, it's not affecting two people. It's affecting every writer who's done that.&lt;/p&gt;

&lt;p&gt;Not every failure was this bug, though. Six domains failed outright: w3schools.com, pcmag.com, united.com, cheapoair.com, drinktrade.com, and beanbox.com. Before trusting my own tool's judgment on those, I checked two of them by hand. w3schools.com sends back a "403 Forbidden," which means it's actively refusing the request. pcmag.com does something similar behind what looks like bot-detection software. Neither of those is the bug I'm measuring. They're sites that block anything that doesn't look like a real browser, which is a completely different, unrelated situation, and my tool correctly recognized that and left them out of the "broken" count instead of lumping them in.&lt;/p&gt;

&lt;p&gt;That distinction turned out to matter more than I expected going in. A tool that can't tell "this site is blocking me" apart from "this site is actually broken" would have either made the 10% number look bigger than it really is, or hidden real bugs inside a pile of unrelated failures.&lt;/p&gt;

&lt;p&gt;I haven't gone back and re-run the original 5-engine test on my own two sites since fixing them. Search engines and AI crawlers don't revisit a site the moment you change something, they come back on their own schedule, which can take days or weeks. Testing again too soon would just show the same zero-citation results as before, but for a completely different reason than last time, and that would be a misleading comparison, not a real one. That follow-up test is its own piece, once enough time has gone by for it to mean something.&lt;/p&gt;

&lt;p&gt;For now, here's what I can say with confidence: this bug is real, it's not rare, and reddit.com has it right now.&lt;/p&gt;




&lt;h2&gt;
  
  
  Where to look
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;The code: &lt;a href="https://github.com/dannwaneri/seo-agent" rel="noopener noreferrer"&gt;github.com/dannwaneri/seo-agent&lt;/a&gt;, specifically &lt;code&gt;modules/ai_crawlability.py&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;The tool behind the domain discovery: &lt;a href="https://www.searchapi.io/?utm_source=github&amp;amp;utm_medium=Ambassador&amp;amp;utm_campaign=dannwaneri.com" rel="noopener noreferrer"&gt;SearchApi's Google search engine&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;*This piece was produced as part of SearchApi's Developer Ambassador program. They provided API credits; I built and tested the integration myself.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>seo</category>
      <category>python</category>
      <category>opensource</category>
    </item>
    <item>
      <title>AI Made Hackathons Easier to Finish. That's the Problem.</title>
      <dc:creator>Daniel Nwaneri</dc:creator>
      <pubDate>Mon, 14 Sep 2026 14:07:49 +0000</pubDate>
      <link>https://dev.to/dannwaneri/ai-made-hackathons-easier-to-finish-thats-the-problem-32ck</link>
      <guid>https://dev.to/dannwaneri/ai-made-hackathons-easier-to-finish-thats-the-problem-32ck</guid>
      <description>&lt;p&gt;Finishing a hackathon project used to mean something. Now it just means you had a good prompt.&lt;/p&gt;

&lt;p&gt;Someone on my timeline said the golden age of hackathons is dead. 2021 to 2023, they said, was the easiest way to build a network, learn fast, and land a job without touching Leetcode. Then AI nuked it. Building got so easy that the "idea guys" took over, and nobody's sure what replaces hackathons for someone starting out today.&lt;/p&gt;

&lt;p&gt;I don't fully agree. But I get exactly what they're mourning.&lt;/p&gt;

&lt;p&gt;Back before AI tools were part of my workflow, I entered a few hackathons, some Google ones among them, and honestly my memory of most of it is pretty vague now. What I do remember clearly is not finishing. Not because the idea was bad. I was stuck in tutorial hell, dealing with imposter syndrome on top of it.&lt;/p&gt;

&lt;p&gt;I still call those hackathons rewarding. Not because I shipped something, most of the time I didn't, but because they showed me what was actually obtainable in this industry. Real working software, built by people my age, solving real problems in a weekend. Before that exposure, "good" was an abstraction. After it, "good" had a shape.&lt;/p&gt;

&lt;p&gt;Right now I'm juggling a handful of hackathons at once. Agents for Humans, AI Builders, the Niger Delta VES fleet build, a few others. The biggest difference from those early days isn't the tech. It's that I actually finish now.&lt;/p&gt;

&lt;p&gt;I get bored of most games after an hour. Building doesn't do that to me. Endless progress. Rewarding. Mine when it's done. People don't always get that building is the relaxing thing, not the job.&lt;/p&gt;

&lt;p&gt;Which is exactly why finishing matters more now, not less. If building is the game, unfinished hackathons are just levels I never beat. At this point somebody needs to electrocute me if I so much as glance at a new project before these are done.&lt;/p&gt;

&lt;p&gt;AI didn't fix tutorial hell. It just made it optional. The blocker isn't syntax anymore, or four hours lost debugging an import error at 2am. That friction is mostly gone.&lt;/p&gt;

&lt;p&gt;But AI enhances more than speed. It enhances your ability to build the wrong thing, confidently, on schedule, with a working demo to show for it.&lt;/p&gt;

&lt;p&gt;That's basically what the "idea guys taking over" complaint is pointing at. Finishing used to be proof you understood what you built, because finishing was hard. Now finishing is available to anyone with a good prompt and no real judgment about whether the thing being built is worth building.&lt;/p&gt;

&lt;p&gt;Tutorial hell was miserable. I'm not nostalgic for it. But debugging your own broken import statement at 2am taught you what actually breaks and why. A working AI-generated demo doesn't.&lt;/p&gt;

&lt;p&gt;I don't think the answer is bringing that friction back on purpose. I don't think the industry has figured out what replaces it either. AI removed the thing that used to force people to learn slowly. Nobody's built the thing that forces people to learn fast without skipping the part where you actually understand what you shipped.&lt;/p&gt;

&lt;p&gt;Hackathons still do the thing that mattered most for me. They show you what's obtainable, who's building it, what the bar actually looks like up close. That part isn't dead. I'm getting it right now, across every one of these I'm juggling.&lt;/p&gt;

&lt;p&gt;What's dead is the assumption that finishing proves anything. Finishing used to mean something. Now it just means you had a good prompt.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>hackathon</category>
      <category>discuss</category>
      <category>productivity</category>
    </item>
    <item>
      <title>I Found Two Bugs in a Hackathon's Judging Tool. Neither Explained Why I Lost.</title>
      <dc:creator>Daniel Nwaneri</dc:creator>
      <pubDate>Mon, 14 Sep 2026 12:47:08 +0000</pubDate>
      <link>https://dev.to/dannwaneri/i-found-two-bugs-in-a-hackathons-judging-tool-neither-explained-why-i-lost-2l4f</link>
      <guid>https://dev.to/dannwaneri/i-found-two-bugs-in-a-hackathons-judging-tool-neither-explained-why-i-lost-2l4f</guid>
      <description>&lt;p&gt;I built StacksNG for the Africa Deep Tech Challenge 2026: an offline coding assistant grounded in the docs of four Nigerian fintech APIs: Paystack, Flutterwave, Monnify, Termii. It runs on retrieval, not memory (RAG, if you know the term): pull the real doc passage before answering, cite the source. 780 scraped chunks, running entirely on-device via llama.cpp, the engine that runs the model locally. No cloud, no API keys, no hallucinated endpoints in payments code.&lt;/p&gt;

&lt;p&gt;I didn't make the semifinals.&lt;/p&gt;

&lt;p&gt;No feedback came with that. So I went looking myself.&lt;/p&gt;




&lt;p&gt;StacksNG shipped &lt;code&gt;qwen2.5-coder-7b&lt;/code&gt;, stock, compressed down to fit in memory (Q4_K_M quantization). That's the largest model that fits the challenge's 8GB envelope, and it's a deliberate bet against the scoring formula, not an oversight. I wrote the ablation into the report myself. A smaller, 1.5B version of the same pipeline wins the formula by about 35 points: faster, lighter, better on the speed and memory-efficiency scores. I rejected it anyway, because on one of my two registered test prompts, the 1.5B opened with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;flutterwave&lt;/span&gt;
&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;flutterwave&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;(...)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That library doesn't exist. The 7B, same retrieval context, wrote real &lt;code&gt;requests&lt;/code&gt; calls against real documented endpoints. I picked correctness over the formula and said so in the report. Fine engineering decision. Turned out to be the wrong thing to be defending.&lt;/p&gt;




&lt;p&gt;I cloned all 20 published semifinalists (full git history, in case anyone privatizes their repo after judging) and read every technical report. SME-Ledger, Jamii Afya, TaxSabi, CodeFellow, ARIS, Homa, all of them: LoRA, QLoRA, distillation, or a merge. Every single one touched the base model somehow.&lt;/p&gt;

&lt;p&gt;StacksNG was the only stock-model architecture in the batch. I noticed the pattern, filed it under "interesting," and moved on. That was the first mistake.&lt;/p&gt;




&lt;p&gt;My own &lt;code&gt;submission.json&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="nl"&gt;"accuracy"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Empty array. Not a low number. No number. &lt;code&gt;git log&lt;/code&gt; on that file shows exactly one commit, never touched again before the deadline. The &lt;code&gt;adtc-profiler&lt;/code&gt; run that produced it either skipped the accuracy gate or &lt;code&gt;lm_eval&lt;/code&gt; wasn't installed at the time. Same failure mode either way: silent, not an error.&lt;/p&gt;

&lt;p&gt;Sacc is 50% of the scoring formula. I'd shipped half the score as zero without meaning to.&lt;/p&gt;




&lt;p&gt;Trying to fix that, I installed &lt;code&gt;lm-eval-harness&lt;/code&gt; and pointed it at my own model through a local &lt;code&gt;llama-server&lt;/code&gt;. It crashed. Every one of 200 requests came back "Invalid logprobs data."&lt;/p&gt;

&lt;p&gt;Two bugs, both confirmed by reading the actual source:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;adtc_profiler&lt;/code&gt;'s own &lt;code&gt;accuracy.py&lt;/code&gt; calls &lt;code&gt;lm_eval&lt;/code&gt; with &lt;code&gt;base_url=local&lt;/code&gt;. Not a URL. lm-eval's GGUF backend needs &lt;code&gt;http://host:port&lt;/code&gt; and POSTs to &lt;code&gt;{base_url}/v1/completions&lt;/code&gt;. As shipped, that line fails for anyone who runs it literally.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;lm-eval-harness&lt;/code&gt;'s GGUF backend expects &lt;code&gt;echo=true&lt;/code&gt; to return logprobs for the whole echoed prompt, the old OpenAI completions behavior. Current &lt;code&gt;llama-server&lt;/code&gt; only returns logprobs for newly generated tokens. The harness hasn't caught up.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I patched both locally. Instead of trusting the broken echo behavior, I forced the model to generate the exact answer text (a grammar rule constraining what it's allowed to output) and read its confidence scores off that generation directly. Ran the hackathon's own default benchmark, a standard general-knowledge multiple-choice test. Got a real number:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;arc_easy acc_norm = 0.74
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Competitive. Ahead of one fine-tuned semifinalist, just behind two others. Plugged into my own formula: &lt;code&gt;Stotal&lt;/code&gt; goes from the submitted 10.23 to 47.23. A 4.6x swing from one missing number, no architecture change.&lt;/p&gt;

&lt;p&gt;I thought that was the end of it.&lt;/p&gt;




&lt;blockquote&gt;
&lt;p&gt;Your submission's originality score did not meet the threshold required to advance.&lt;/p&gt;

&lt;p&gt;Originality Score (0–10): &lt;strong&gt;3&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Model Origin (Originality Review): Stock model, used as-is: Qwen2.5-Coder-7B-Instruct-GGUF from lmstudio-community's official quantization.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Round 1 runs an originality gate before any technical scoring even happens. Template compliance: fine. The RAG architecture, the citation grounding, the 780-chunk corpus: not mentioned. The problem was never Sacc. The problem was that I never touched the model.&lt;/p&gt;

&lt;p&gt;All the archaeology (the broken &lt;code&gt;base_url&lt;/code&gt;, the echo/logprobs mismatch, the 0.74) was real, reproducible, and completely beside the point. I'd spent a week debugging the wrong layer.&lt;/p&gt;




&lt;p&gt;The pattern was sitting in my own data the whole time. Eighteen other teams fine-tuned. I read that fact, found it interesting, and filed it as a scoring-formula tradeoff instead of what it actually was: everyone else had cleared a bar I didn't know existed.&lt;/p&gt;

&lt;p&gt;That's the uncomfortable part. The evidence was already in front of me before the rejection email arrived. I reasoned right past it anyway.&lt;/p&gt;

&lt;p&gt;I was looking at the parts of the system I could inspect: the formula, the profiler source, the tooling. The part I couldn't see was the one that mattered: a review that runs above all of it, undocumented in any code or rule I had access to.&lt;/p&gt;

&lt;p&gt;A RAG layer grounding a stock model is a defensible product decision. It is not, to an originality reviewer, evidence that you built anything. Those are different bars, and clearing one says nothing about the other.&lt;/p&gt;




&lt;p&gt;If you're entering a hackathon with a scoring formula published anywhere: read it. Optimize against it. But ask what isn't in the formula too. A judging process can have a gate that never shows up in the rubric or the tooling. The only way to find it is to assume it exists and ship something that survives it anyway.&lt;/p&gt;

&lt;p&gt;Ship a real modification. Even a light LoRA pass on your own corpus would have cleared this. The application layer was good work: retrieval, citations, the deterministic-gate bugs I fixed after a DEV.to reader found them in production. None of it was ever going to be evidence that the model itself was mine.&lt;/p&gt;

&lt;p&gt;Good luck to the eighteen who advanced. You did the part I skipped.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;StacksNG: github.com/dannwaneri/stacksng. Two tooling bugs found during this postmortem, filed upstream: &lt;a href="https://github.com/Africa-Deep-Tech-Foundation/adtc-profiler/issues/4" rel="noopener noreferrer"&gt;adtc-profiler#4&lt;/a&gt; and &lt;a href="https://github.com/EleutherAI/lm-evaluation-harness/issues/4158" rel="noopener noreferrer"&gt;lm-evaluation-harness#4158&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>hackathon</category>
      <category>opensource</category>
      <category>debugging</category>
    </item>
    <item>
      <title>I Hid a Rule in CLAUDE.md. Only One Reviewer Could Prove It Read It.</title>
      <dc:creator>Daniel Nwaneri</dc:creator>
      <pubDate>Wed, 09 Sep 2026 14:40:04 +0000</pubDate>
      <link>https://dev.to/dannwaneri/i-hid-a-rule-in-claudemd-only-one-reviewer-could-prove-it-read-it-4ik9</link>
      <guid>https://dev.to/dannwaneri/i-hid-a-rule-in-claudemd-only-one-reviewer-could-prove-it-read-it-4ik9</guid>
      <description>&lt;p&gt;"Does your AI code reviewer read CLAUDE.md?" is the wrong question to ask. Almost any of them will say yes, and honestly, most will catch something. What actually matters is whether you can tell the difference between a tool that read your rule and a tool that just happens to agree with it.&lt;/p&gt;

&lt;p&gt;So I built a small API, wrote real rules into &lt;code&gt;CLAUDE.md&lt;/code&gt; and &lt;code&gt;AGENTS.md&lt;/code&gt;, and then broke one of those rules twice on purpose. The first break was something any decent security scanner would flag anyway. The second one had no reason to get caught unless a tool actually opened the file and read it. I ran both through &lt;a href="https://coderabbit.ai" rel="noopener noreferrer"&gt;CodeRabbit&lt;/a&gt; and &lt;a href="https://qodo.ai" rel="noopener noreferrer"&gt;Qodo&lt;/a&gt; to see what would happen.&lt;/p&gt;

&lt;p&gt;Both tools caught the second one eventually. Only one of them could tell me why.&lt;/p&gt;




&lt;h2&gt;
  
  
  the setup
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/dannwaneri/rules-demo-api" rel="noopener noreferrer"&gt;&lt;code&gt;rules-demo-api&lt;/code&gt;&lt;/a&gt; is a small Cloudflare Worker. &lt;code&gt;POST /feedback&lt;/code&gt; takes a rating and a message, validates them, and logs the rating. Both &lt;code&gt;CLAUDE.md&lt;/code&gt; and &lt;code&gt;AGENTS.md&lt;/code&gt; document the same four rules for the repo, including this one:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Never log request headers or bodies. They may contain secrets, tokens, or PII."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;and one I added later:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"All new route paths must be kebab-case (&lt;code&gt;/feedback-summary&lt;/code&gt;), never camelCase (&lt;code&gt;/feedbackSummary&lt;/code&gt;). This is an internal convention, not a general best practice."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I wrote that second rule that way on purpose. There's nothing about kebab-case routes that's a security issue, and nothing about it is a bug either — it's just a house style choice with zero backing outside this one repo. Which is exactly what makes it a cleaner test than the first rule. If a tool flags a violation of this one, it had to have read the file, because there's nothing else it could be catching. It's a small example, but this is exactly the kind of gap that shows up once you're running &lt;a href="https://www.qodo.ai/academy/ai-code-review/" rel="noopener noreferrer"&gt;AI code review at scale&lt;/a&gt; instead of just eyeballing one repo yourself.&lt;/p&gt;




&lt;h2&gt;
  
  
  test one: the rule with a built-in excuse
&lt;/h2&gt;

&lt;p&gt;I opened a PR that logs the full request headers and body, with a commit message saying it was "temporary, to debug a client issue." Both tools caught it right away.&lt;/p&gt;

&lt;p&gt;Here's what Qodo said:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Rule violation, High: Customer secrets enter application logs&lt;/strong&gt;&lt;br&gt;
"Rule 1: Do Not Log Unredacted Request Headers or Bodies" (&lt;code&gt;AGENTS.md[5-8]&lt;/code&gt;)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And CodeRabbit:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"This change can expose credentials and private feedback content through application logs. It should not merge until logging is restricted to an allowlist of necessary diagnostic fields." Merge Risk: High.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Both correct, and only one of them names the actual file. But this test doesn't really prove anything about file-reading either way: logging raw headers and bodies is a well-known bad practice whether or not you've written it down anywhere. Any half-decent scanner flags this on instinct alone. Call this round a wash.&lt;/p&gt;




&lt;h2&gt;
  
  
  test two: the rule with no excuse
&lt;/h2&gt;

&lt;p&gt;Same repo, new PR. This time I added a &lt;code&gt;GET /feedbackSummary&lt;/code&gt; endpoint in camelCase, breaking the kebab-case rule and nothing else. No security issue, no bug, just a naming convention nobody outside this repo has ever heard of. The only way to catch it is to have actually read &lt;code&gt;CLAUDE.md&lt;/code&gt; or &lt;code&gt;AGENTS.md&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Qodo caught it immediately:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Rule violation, High: Clients cannot use the kebab-case route&lt;/strong&gt;&lt;br&gt;
"Rule 4: Use Kebab-Case for New API Route Paths" (&lt;code&gt;AGENTS.md[7-10]&lt;/code&gt;, &lt;code&gt;CLAUDE.md[7-10]&lt;/code&gt;)&lt;br&gt;
"Requests to &lt;code&gt;/feedback-summary&lt;/code&gt;... do not match the condition and fall through to the handler's 404 response."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It even took the reasoning a step further than I expected. Since the handler 404s on anything unmatched, Qodo worked out that a client calling the &lt;em&gt;correct&lt;/em&gt; kebab-case URL would get a 404, while my wrong, camelCase URL worked fine. That's not just matching my code against a style guide. That's tracing what the naming mismatch does once real requests hit it.&lt;/p&gt;

&lt;p&gt;CodeRabbit, running on its default "Chill" profile, had a different take:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"No actionable comments were generated in the recent review. 🎉" Merge Risk: Minimal.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Nothing. A clean bill of health on a PR that breaks a rule written into both files in the same repo CodeRabbit had just finished reviewing.&lt;/p&gt;




&lt;h2&gt;
  
  
  the profile problem
&lt;/h2&gt;

&lt;p&gt;Before I wrote CodeRabbit off, I went and checked its settings. There's a Chill/Assertive toggle in there, and Assertive is documented to surface more style-level feedback. So I switched it over and forced a full re-review on the same PR.&lt;/p&gt;

&lt;p&gt;This time, it caught the naming issue:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Update the route condition... to match the required kebab-case path &lt;code&gt;/feedback-summary&lt;/code&gt; instead of &lt;code&gt;/feedbackSummary&lt;/code&gt;."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;No mention of &lt;code&gt;CLAUDE.md&lt;/code&gt;. No mention of &lt;code&gt;AGENTS.md&lt;/code&gt;. No rule number, nothing. Just "the required kebab-case path," stated like a fact everyone already knows.&lt;/p&gt;

&lt;p&gt;And that's fair, actually, because kebab-case for REST routes really is a common convention across the web, even though I'd framed it here as an arbitrary house rule specific to this repo. So CodeRabbit catching it on Assertive doesn't prove it read my file at all. It's just as easily explained by a stricter style-sensitivity dial reaching for a convention it already knew before it ever saw my repo. I picked a rule I thought had zero generic backing, and I got that part slightly wrong. Better to admit that than pretend the test was cleaner than it was.&lt;/p&gt;




&lt;h2&gt;
  
  
  what separates them
&lt;/h2&gt;

&lt;p&gt;It's not catch versus miss. Both tools eventually caught the same thing. What's different between them is whether that catch is checkable.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://youtu.be/_QlBOdiATxU" rel="noopener noreferrer"&gt;https://youtu.be/_QlBOdiATxU&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Every single Qodo finding across both PRs, no matter the severity, points to a specific rule number and a line range in a named file. Each finding also carries its own severity rating, the exact code it's flagging, the reasoning behind the call, and a fix prompt you can hand straight to your own agent. You can click the link yourself. You can go read the rule and confirm it says what Qodo claims it says. I don't have to take any of that on faith.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Update, Sept 10:&lt;/strong&gt; A reader, pm25coder, pushed back on that last claim in the comments, and correctly. A citation proves a rule was surfaced, not that it drove the decision or that it's still accurate. They proposed a test: move the rule to a different line, change nothing else, and re-run the same PR. If the citation tracks the move, it's a live read. If it doesn't, it's reconstructed from matched text.&lt;/p&gt;

&lt;p&gt;I ran it. I shifted the kebab-case rule seven lines down in both &lt;code&gt;AGENTS.md&lt;/code&gt; and &lt;code&gt;CLAUDE.md&lt;/code&gt;, forced a fresh review on the same PR, and Qodo's new finding still cited the old range: &lt;code&gt;AGENTS.md[7-10]&lt;/code&gt;, &lt;code&gt;CLAUDE.md[7-10]&lt;/code&gt;. Lines 7-10 no longer contain that rule. The citation didn't update.&lt;/p&gt;

&lt;p&gt;So the claim above is wrong as stated. You can click the link, but nothing guarantees it still points at the right lines once the file changes underneath it. That's a real gap, not a nitpick, and it's the same failure this whole article is about, just aimed back at itself.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;CodeRabbit's findings are real, and often genuinely well-worded, but they never show their source. Whether a given catch came from actually reading &lt;code&gt;AGENTS.md&lt;/code&gt;, or from a stricter profile dial turned up one notch, there's just no way to tell from what it hands you. Flipping Chill to Assertive proves the sensitivity is configurable. It doesn't prove the source of any single catch is your file.&lt;/p&gt;

&lt;p&gt;At one repo, honestly, that difference barely matters. You'll eyeball the PR either way. But once you're running this across dozens of repos, with rules that get written and updated by different people over time, "did the reviewer actually check the current rule" stops being a question you can shrug off. An unauditable catch and an unauditable miss look exactly the same from the outside. You won't know which one you're getting until it's already gone wrong.&lt;/p&gt;




&lt;h2&gt;
  
  
  takeaway
&lt;/h2&gt;

&lt;p&gt;Ask a different question than "does it read CLAUDE.md." Ask "can it show me the line it read." One of these tools answers that on every single finding, no exceptions. The other never answers it, no matter how many settings you turn up. That's the dimension actually worth comparing tools on, more than any feature list — see &lt;a href="https://www.qodo.ai/academy/ai-code-review-tools-comparison-and-benchmarks/" rel="noopener noreferrer"&gt;AI code review tools comparison and benchmarks&lt;/a&gt; if you want the fuller picture beyond just these two.&lt;/p&gt;

&lt;p&gt;A rules file sitting in a repo is best-effort compliance at best: readable by a human if they bother to look, but enforced inconsistently depending on whatever's reviewing it that day and whatever profile happens to be set. A rule that's centrally managed and cited by line number is a different thing entirely. Qodo actually imports rules straight out of &lt;code&gt;AGENTS.md&lt;/code&gt; and &lt;code&gt;CLAUDE.md&lt;/code&gt; into its own centrally managed Review Standards, where each one picks up a severity and a scope of its own. It's auditable across every repo it touches, whether that's one repo or two hundred of them.&lt;/p&gt;

&lt;p&gt;Qodo's own &lt;a href="https://www.qodo.ai/features/qodo-agentic-toolbox/" rel="noopener noreferrer"&gt;Agentic Toolbox&lt;/a&gt;, launching September 9th, pushes this a step further. One of its skills, &lt;code&gt;qodo-get-rules&lt;/code&gt;, loads the workspace's centrally-managed rules straight into the agent's session before it writes a single line, not just at review time after the fact. If a rule is worth citing by line number in a PR, it's worth putting in front of the agent before the PR exists.&lt;/p&gt;

&lt;p&gt;I hid a rule in a file that most tools claim to read. Getting caught wasn't the hard part. Proving it wasn't a coincidence was.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Repo: &lt;a href="https://github.com/dannwaneri/rules-demo-api" rel="noopener noreferrer"&gt;rules-demo-api&lt;/a&gt;. Both tools run on their respective free/trial tiers for public repos.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>codereview</category>
      <category>discuss</category>
      <category>devtool</category>
    </item>
    <item>
      <title>My Grandmother Ran Ajo. I Built the Version Where the Pot Can't Walk Away</title>
      <dc:creator>Daniel Nwaneri</dc:creator>
      <pubDate>Sun, 06 Sep 2026 22:19:59 +0000</pubDate>
      <link>https://dev.to/dannwaneri/my-grandmother-ran-ajo-i-built-the-version-where-the-pot-cant-walk-away-5gkn</link>
      <guid>https://dev.to/dannwaneri/my-grandmother-ran-ajo-i-built-the-version-where-the-pot-cant-walk-away-5gkn</guid>
      <description>&lt;p&gt;This is a submission for &lt;a href="https://dev.to/challenges/weekend-2026-09-03"&gt;Weekend Challenge: Generosity Edition&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;My late grandmother was a trader. She ran ajo for years, the way a lot of Nigerian market women did, and used her payout round to restock her shop. More stock meant more sales meant more saved for the next round. It worked, until the round it didn't: she told me about a time the person holding the pot borrowed against it to solve a family problem and couldn't pay it back in time. He didn't vanish maliciously. He just couldn't make the group whole again, and the circle absorbed the loss.&lt;/p&gt;

&lt;p&gt;That's the actual failure mode. Not fraud, most of the time. Just one person holding money that isn't theirs, under pressure, with no enforcement above their own word.&lt;/p&gt;

&lt;p&gt;DEV framed this challenge around ethical and accountable giving, and equity and inclusion. Ajo isn't charity. Nobody donates and walks away. Every member eventually gets their own money back, plus one round early. But it's the original financial-inclusion tool for people the banks never served: market traders, mostly women, building credit and liquidity together because nobody else would. My grandmother didn't have a bank account for this. She had her circle.&lt;/p&gt;

&lt;p&gt;Ajo Chain is my answer to that specific story. Before I wrote a line of code, I set one rule: if you deleted a technology, would something actually break, or would the project just look less polished? Everything that failed that test got cut. One whole technology did, and I'll get to why.&lt;/p&gt;

&lt;p&gt;Ajo (also called esusu) is a rotating savings circle. Five people, one fixed amount each round, one payout that rotates to a different person every round.&lt;/p&gt;

&lt;p&gt;Ajo Chain doesn't digitize the spreadsheet. It replaces the one person everyone has to trust with a Solana program that can't disappear. Escrow, rotation order, and default tracking all live on-chain. Contributions only release to the next member in the fixed order, and only once all five have paid. A missed deadline gets flagged permanently. It's visible forever, not something a moderator can quietly erase later.&lt;/p&gt;

&lt;p&gt;The part I didn't expect going in: the smart contract can tell you a contribution didn't arrive. It can't tell you &lt;em&gt;why&lt;/em&gt;. So I added one narrow Gemini agent whose only job is reading the on-chain evidence around a disputed default and drafting a plain-language note for a human moderator, distinguishing "the transfer failed on-chain" from "this member shows no attempt to pay." It never finalizes anything. It drafts, a human decides.&lt;/p&gt;

&lt;p&gt;Devnet only. No real fund custody.&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;Live transparency page, still on devnet, walking a real completed round (four members on time, one deliberate default, Gemini's note, a late catch-up, payout released):&lt;/p&gt;

&lt;p&gt;&lt;a href="https://ajo-chain-worker.fpl-test.workers.dev/status/3XrTDiMxhznfBZLMxDrEVndP8YKwR1kCaN9HpeUxGRne" rel="noopener noreferrer"&gt;https://ajo-chain-worker.fpl-test.workers.dev/status/3XrTDiMxhznfBZLMxDrEVndP8YKwR1kCaN9HpeUxGRne&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/fzJFBgV5Uh4" width="710" height="399"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/dannwaneri/ajo-chain" rel="noopener noreferrer"&gt;https://github.com/dannwaneri/ajo-chain&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Program ID: &lt;code&gt;9RGBLQCcQnsiCdu9RcmEzG4SvihkfTvi1KMz98KCWPoT&lt;/code&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How I Built It
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Solana is the enforcement layer
&lt;/h3&gt;

&lt;p&gt;This is the only part I'd call load-bearing without qualification. Each group gets an escrow PDA and a fixed rotation order set once at creation. Four instructions: &lt;code&gt;create_group&lt;/code&gt;, &lt;code&gt;contribute&lt;/code&gt;, &lt;code&gt;mark_default&lt;/code&gt;, &lt;code&gt;release_payout&lt;/code&gt;. &lt;code&gt;release_payout&lt;/code&gt; won't move a lamport until all five members have contributed, and it pays out to exactly one address: whoever is next in the array, checked directly in the handler, not just in an account constraint I could talk myself into trusting.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="nd"&gt;require!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;group&lt;/span&gt;&lt;span class="py"&gt;.has_contributed&lt;/span&gt;&lt;span class="nf"&gt;.iter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.all&lt;/span&gt;&lt;span class="p"&gt;(|&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nn"&gt;AjoError&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;NotAllContributed&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nd"&gt;require!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="py"&gt;.accounts.recipient&lt;/span&gt;&lt;span class="nf"&gt;.key&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;group&lt;/span&gt;&lt;span class="py"&gt;.members&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;group&lt;/span&gt;&lt;span class="py"&gt;.round_number&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nb"&gt;usize&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="nn"&gt;AjoError&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;InvalidRecipient&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both checks run before the transfer, not after. There's no &lt;code&gt;recipient&lt;/code&gt; field a caller controls; the address is read out of the group's own on-chain state. Delete this layer and you're back to trusting one person with the pot. That's the whole reason the project exists.&lt;/p&gt;

&lt;p&gt;I tested it with litesvm instead of a local validator. Three tests, in-process, no faucet, no network: a full happy-path round, a deliberate default followed by a late catch-up that still completes the round, and rejection of non-members and double-contributions.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;running 3 tests
test deliberate_default_is_flagged_and_blocks_payout ... ok
test full_round_cycle_pays_correct_rotation_member ... ok
test rejects_non_members_and_double_contribution ... ok

&lt;/span&gt;&lt;span class="gp"&gt;test result: ok. 3 passed;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;0 failed&lt;span class="p"&gt;;&lt;/span&gt; 0 ignored&lt;span class="p"&gt;;&lt;/span&gt; 0 measured&lt;span class="p"&gt;;&lt;/span&gt; 0 filtered out&lt;span class="p"&gt;;&lt;/span&gt; finished &lt;span class="k"&gt;in &lt;/span&gt;1.05s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Full source in &lt;a href="https://github.com/dannwaneri/ajo-chain/blob/main/programs/ajo_chain/tests/ajo_chain_test.rs" rel="noopener noreferrer"&gt;&lt;code&gt;programs/ajo_chain/tests/ajo_chain_test.rs&lt;/code&gt;&lt;/a&gt; if you want to run it yourself: &lt;code&gt;cargo test --manifest-path programs/ajo_chain/Cargo.toml&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Then I deployed to devnet and ran the same story for real, five contributions and one payout, with real signatures and real Explorer links. Somewhere in there I also learned that &lt;code&gt;api.devnet.solana.com&lt;/code&gt; blocks Cloudflare's IP ranges outright, which nobody documents until you hit it. QuickNode's free devnet endpoint doesn't have that problem.&lt;/p&gt;

&lt;h3&gt;
  
  
  Gemini is the fairness layer, and I'm going to undersell it on purpose
&lt;/h3&gt;

&lt;p&gt;It reads the group's on-chain round state plus the disputed member's recent transaction history, then drafts a short note: did the evidence show an attempt that failed, or no attempt at all? That's my grandmother's story again, the same question a human circle asked about the same kind of person, just with transaction signatures instead of memory and reputation. Two different load-bearing questions here, and they get two different answers. For the project as a whole: no, delete Gemini and the Solana program is untouched, nobody loses money, nobody gets locked out. For the dispute-adjudication feature itself: yes. Delete Gemini and a real ajo moderator is staring at raw transaction signatures and &lt;code&gt;err&lt;/code&gt; fields, trying to work out whether someone tried and failed or just didn't show up. That's not information a non-technical person can act on. The feature doesn't survive losing it. The product does.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cloudflare Worker + D1 makes the first two legible to a human
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;POST /sync&lt;/code&gt; indexes a group's on-chain state and its full transaction log into D1. &lt;code&gt;POST /disputes&lt;/code&gt; runs the Gemini trigger and stores the result. &lt;code&gt;GET /status/:group&lt;/code&gt; is the public page: rotation order, per-round status, the permanent default flag, every transaction linked to Explorer, and the dispute note sitting right there next to it. That page is the actual submission, more than any single instruction is.&lt;/p&gt;

&lt;h3&gt;
  
  
  What I cut: ElevenLabs
&lt;/h3&gt;

&lt;p&gt;The original plan had a fifth member with no text fallback at all, a voice note as their only interface to the circle. I got as far as an API key, a voice ID, a working TTS call, and then found out ElevenLabs' free tier blocks API access to any premade or library voice. Only a self-cloned voice or a paid plan gets through. I tried Instant Voice Cloning next, got most of the way through the upload flow, and the clone never actually saved. At that point I had a choice: keep spending hours on an integration, or ship a submission that's honest about what it does well. I cut it. The spec file in the repo still has the original design, timestamped.&lt;/p&gt;

&lt;h3&gt;
  
  
  The bug I almost shipped
&lt;/h3&gt;

&lt;p&gt;Writing an adversarial-test brief for a security pass, I re-read my own &lt;code&gt;/status&lt;/code&gt; page code and noticed the dispute note (free-form text Gemini writes) was going straight into HTML with no escaping, on a page anyone can write to since &lt;code&gt;/disputes&lt;/code&gt; has no auth. I fixed it, then verified the fix by injecting a real &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; tag directly into the database and confirming it rendered as inert text, not a live tag. That fix is commit &lt;code&gt;659d6da&lt;/code&gt; if you want to see the diff.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prize Categories
&lt;/h2&gt;

&lt;p&gt;Submitting to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Best Use of Solana&lt;/strong&gt; — the escrow, rotation, and default enforcement is the mechanism the whole project exists to provide, not a wrapper around a database&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Best Use of Google AI&lt;/strong&gt; — one narrow Gemini trigger, reasoning over real on-chain evidence, that drafts and never finalizes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Not submitting for ElevenLabs. See above.&lt;/p&gt;




&lt;p&gt;Devnet only, no real fund custody, and the on-chain program is the piece I'd stake the submission on. Everything else in this stack exists to make that piece visible and fair to the people actually running the circle.&lt;/p&gt;

</description>
      <category>weekendchallenge</category>
      <category>devchallenge</category>
      <category>solana</category>
      <category>gemini</category>
    </item>
    <item>
      <title>I Found 3 Security Vulnerabilities in My Own AI Agent's Tool Access</title>
      <dc:creator>Daniel Nwaneri</dc:creator>
      <pubDate>Wed, 02 Sep 2026 13:19:03 +0000</pubDate>
      <link>https://dev.to/dannwaneri/i-found-3-security-vulnerabilities-in-my-own-ai-agents-tool-access-75m</link>
      <guid>https://dev.to/dannwaneri/i-found-3-security-vulnerabilities-in-my-own-ai-agents-tool-access-75m</guid>
      <description>&lt;p&gt;I built GeoMart for OpenAI's WebMCP Challenge: a storefront where a human fills in a live, unsubmitted "site brief" and an AI agent uses WebMCP tools to read it, score survey equipment against real physical constraints, and draft a quote the human has to approve. The hackathon's own rule is blunt: every core action has to be reachable only through a WebMCP tool, no REST route allowed to duplicate it.&lt;/p&gt;

&lt;p&gt;I thought I'd covered that. Then I asked Google Antigravity to try to break it, gave it the full source code, and it found three problems in about ten minutes.&lt;/p&gt;

&lt;p&gt;Antigravity's first pass found that agent-supplied text, the quote reasoning, the product IDs in the comparison tray, was going straight into the page via unescaped &lt;code&gt;innerHTML&lt;/code&gt;. It also found something worse: &lt;code&gt;POST /api/quotes&lt;/code&gt;, the endpoint that creates a quote request, had zero protection. A bare script from outside any browser could call it directly and submit a quote with no human involved at all.&lt;/p&gt;

&lt;p&gt;I fixed both. HTML-escaped every agent-supplied string before it hits the DOM. And added an &lt;code&gt;isTrusted&lt;/code&gt; check on the submit button's click handler, so a script-simulated click (&lt;code&gt;element.click()&lt;/code&gt;, &lt;code&gt;dispatchEvent&lt;/code&gt;) can't fire it. Only a hardware-derived click passes.&lt;/p&gt;

&lt;p&gt;I redeployed, felt good about it, and asked for a retest.&lt;/p&gt;

&lt;p&gt;Antigravity replayed the exact attack that had worked before. This time it got a 403. Good. Then it tried something I hadn't thought to test: it manually set the &lt;code&gt;Origin&lt;/code&gt; header on a bare Node.js &lt;code&gt;fetch()&lt;/code&gt; call to match my site's own domain.&lt;/p&gt;

&lt;p&gt;It got a 201. The quote submitted. No browser involved.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Origin&lt;/code&gt; is not a secret. My repo is open source, required by the hackathon's own rules. Anyone reading &lt;code&gt;worker.ts&lt;/code&gt; can see exactly what value my check expects. A real browser can't forge that header, but Node's &lt;code&gt;fetch()&lt;/code&gt; isn't a browser and has no such restriction. My "fix" only ever checked "did you bother to copy the domain name," not "are you actually a browser."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If your security check would still pass after you told the attacker exactly how it works, it was never a real check.&lt;/strong&gt; It was a filter for people who hadn't read your code yet.&lt;/p&gt;

&lt;p&gt;I considered a session-cookie approach next and talked myself out of it in about two minutes, because it fails the same test: a script with full HTTP access can just fetch the page first to grab the cookie, then replay it on the real request. No browser needed there either. Same hole, different shape.&lt;/p&gt;

&lt;p&gt;The fix that worked needed something that never appears in the repo. I added Cloudflare Turnstile, verified server-side against a secret stored only in a Worker secret, never committed, never in the client bundle. I replayed the exact bypass afterward: correct Origin, no token, 403. Correct Origin, fake token, 403. That's the difference between "a value you can read" and "a value you can't."&lt;/p&gt;

&lt;p&gt;While chasing the Origin bypass, the retest surfaced something unrelated: the database table for storing submitted quotes had never been created. I'd written the migration file back when I built the feature and never run it. Every previous test of the "human clicks submit" flow had only exercised the client-side draft step, &lt;code&gt;draft_quote_notes&lt;/code&gt;, which never touches the server. The whole point of the human-in-the-loop design is that a human's click writes a row to the database. That write had been silently broken the entire time, and nothing caught it until a click produced a 500 error.&lt;/p&gt;

&lt;p&gt;That whole chase left me with one test I now apply to every check I add: does this check depend on something the agent, or anyone reading your public repo, could already know or derive?&lt;/p&gt;

&lt;p&gt;Origin headers: public, in your own URL.&lt;br&gt;
Referer headers: same problem.&lt;br&gt;
A session cookie with no server-side validation: derivable in two requests.&lt;br&gt;
A value only your server computes, that never leaves your server: actually a secret.&lt;/p&gt;

&lt;p&gt;Adversarial testing an AI-agent-facing app isn't optional if you're claiming a human-approval boundary. Ask an agent with full source access to try to defeat your own claims, specifically the ones you're proudest of, before a judge or an attacker does it for you.&lt;/p&gt;

&lt;p&gt;GeoMart is live at &lt;a href="https://geomart-webmcp.fpl-test.workers.dev" rel="noopener noreferrer"&gt;https://geomart-webmcp.fpl-test.workers.dev&lt;/a&gt;, the WebMCP tools are documented in the repo at &lt;a href="https://github.com/dannwaneri/geomart-webmcp" rel="noopener noreferrer"&gt;https://github.com/dannwaneri/geomart-webmcp&lt;/a&gt;, and the commit message on the security-hardening commit walks through every one of these fixes, including the ones that didn't work the first time.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>opensource</category>
      <category>security</category>
      <category>webmcp</category>
    </item>
  </channel>
</rss>
