<?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: Mohamed</title>
    <description>The latest articles on DEV Community by Mohamed (@moaz06).</description>
    <link>https://dev.to/moaz06</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%2F4016053%2Fe1c13bcb-5f59-4498-9e87-640905fbdca8.png</url>
      <title>DEV Community: Mohamed</title>
      <link>https://dev.to/moaz06</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/moaz06"/>
    <language>en</language>
    <item>
      <title>[Boost]</title>
      <dc:creator>Mohamed</dc:creator>
      <pubDate>Fri, 17 Jul 2026 17:42:43 +0000</pubDate>
      <link>https://dev.to/moaz06/-32gm</link>
      <guid>https://dev.to/moaz06/-32gm</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/moaz06/a-200-is-not-a-read-2on" class="crayons-story__hidden-navigation-link"&gt;A 200 Is Not a Read&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="/moaz06" 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%2F4016053%2Fe1c13bcb-5f59-4498-9e87-640905fbdca8.png" alt="moaz06 profile" class="crayons-avatar__image"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/moaz06" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Mohamed
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Mohamed
                
              
              &lt;div id="story-author-preview-content-4168592" 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="/moaz06" 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%2F4016053%2Fe1c13bcb-5f59-4498-9e87-640905fbdca8.png" class="crayons-avatar__image" alt=""&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Mohamed&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/moaz06/a-200-is-not-a-read-2on" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Jul 17&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/moaz06/a-200-is-not-a-read-2on" id="article-link-4168592"&gt;
          A 200 Is Not a Read
        &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/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/security"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;security&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/moaz06/a-200-is-not-a-read-2on" 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/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;1&lt;span class="hidden s:inline"&gt;&amp;nbsp;reaction&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/moaz06/a-200-is-not-a-read-2on#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

              1&lt;span class="hidden s:inline"&gt;&amp;nbsp;comment&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;
            6 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial crayons-icon c-btn__icon"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success crayons-icon c-btn__icon"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


</description>
    </item>
    <item>
      <title>A 200 Is Not a Read</title>
      <dc:creator>Mohamed</dc:creator>
      <pubDate>Fri, 17 Jul 2026 17:42:25 +0000</pubDate>
      <link>https://dev.to/moaz06/a-200-is-not-a-read-2on</link>
      <guid>https://dev.to/moaz06/a-200-is-not-a-read-2on</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; Your AI agent will answer from a page it never actually read, with a straight face, and the transcript won't tell you. An HTTP 200 means the bytes arrived, not that you got the page. FetchGate turns "did we actually read it?" into a deterministic RETRIEVED / FAILED / UNKNOWN verdict, and hard-fails before the model can bluff.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Give an AI agent a web tool and watch what happens when a fetch fails quietly instead of cleanly. The page comes back as an empty JavaScript shell, a Cloudflare "just a moment" screen, or a 403 dressed up as a 200, and the agent does not stop to notice. It answers anyway, from whatever it already had in its weights, and nothing in the transcript tells you it never read the page. What you get is a confident guess wearing the costume of a retrieval.&lt;/p&gt;

&lt;p&gt;I keep coming back to this shape: a tool answers one question with confidence and leaves a second, more important one unanswered. This time the second question is the smallest and the sharpest one I have worked on: did we actually read the page, yes or no?&lt;/p&gt;

&lt;h2&gt;
  
  
  The visible axis: tokens
&lt;/h2&gt;

&lt;p&gt;The popular version of this problem is about cost. A raw article can be tens of thousands of tokens, so people measure it, complain about it, and reach for cleaner extraction and stealth browsers to shrink it. That cost is real, and it is being handled.&lt;/p&gt;

&lt;p&gt;But cost was never the dangerous part. Context windows grew, extraction improved, and the cost problem mostly dissolved. The dangerous part sat underneath it the whole time and never moved: whether the fetch produced a read at all.&lt;/p&gt;

&lt;h2&gt;
  
  
  The invisible axis: whether the fetch happened
&lt;/h2&gt;

&lt;p&gt;The failure itself is simple. A model is handed no content while a question is waiting to be answered, so it fills the gap. That is what models do; it is not misbehavior. The bug is not the model guessing but the harness that lets an empty retrieval reach a final, grounded-sounding answer.&lt;/p&gt;

&lt;p&gt;It shows up in three ways:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the fetch returns nothing and the model answers from its training weights,&lt;/li&gt;
&lt;li&gt;the fetch returns nothing and the model answers from a stale prior it happens to hold,&lt;/li&gt;
&lt;li&gt;the fetch succeeds, technically, but returns the wrong content (a challenge page, a consent wall, a soft 404), and the model reads that and answers as if it read the article.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What I am building here is not new groundedness work, and I want to be honest about the neighbors. RAG faithfulness checks like RAGAS and TruLens, abstention work on knowing what you don't know, and citation UX like Perplexity's "could not access this source" all live near this problem and earn their keep. But they evaluate whatever text arrived, after the fact; none of them assert that a fetch of the intended resource actually happened. That narrow delta is the whole point: a check at the fetch boundary, upstream of grounding, that hard-fails before the model ever gets to answer.&lt;/p&gt;

&lt;h2&gt;
  
  
  The name of the mistake
&lt;/h2&gt;

&lt;p&gt;The mistake is treating an HTTP 200 as evidence. A 200 means the bytes arrived; it says nothing about whether those bytes are the page you asked for or a wall standing in front of it. A JavaScript app returns a 200 whose body is almost all script and almost no readable text. Cloudflare returns a 200 with a full "verify you are human" page. Both pass every naive check, which is why the question cannot be "did the request succeed" but has to be "did we come away with the page".&lt;/p&gt;

&lt;h2&gt;
  
  
  The gate, not the fields
&lt;/h2&gt;

&lt;p&gt;The obvious fix is to hand the model more metadata: the status code, byte counts, a note that the page looked empty. It does not work, because richer fields the model can read are richer fields the model can ignore. The model will wave past a byte count of zero the same way it waves past an empty body. If it gets a vote on whether it read the page, you have handed the decision back to the component you did not trust in the first place.&lt;/p&gt;

&lt;p&gt;So the decision lives outside the model, in the orchestration layer, as a deterministic gate. The gate reads what the fetch runtime measured and returns one of three verdicts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;RETRIEVED: we can point to real, sufficient content. The model may answer and cite it.&lt;/li&gt;
&lt;li&gt;FAILED: we can point to a definite reason there is no read, such as a 4xx or 5xx, a timeout, an HTTPS to HTTP downgrade, or an empty body. The agent stops.&lt;/li&gt;
&lt;li&gt;UNKNOWN: we cannot classify it either way, often because the page is a JavaScript app that needs rendering. The agent stops, or renders and checks again.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Three layers feed the verdict, and the strictest of them wins. The transport layer looks at the status and the redirect chain. The extraction layer keeps readable text separate from raw bytes, so that a 17-byte price JSON counts as a read while a 400 KB script shell does not. The third layer, a content-validity fingerprint, watches for the walls. It is a best-effort heuristic that will rot over time, so it is only ever allowed to pull a verdict down to UNKNOWN; it can never manufacture a read, and the only path to a hard FAILED is a transport signal. The gate fails closed or fails labeled; it never fails fluent.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where the browser earns its weight
&lt;/h2&gt;

&lt;p&gt;For pages that are genuinely empty to a static reader, the gate escalates to a real headless browser, lets the page render, and classifies again. The point is to render, not to evade: a page that is still walled after an honest render stays UNKNOWN and hard-fails. The gate opens a browser tab the way a person would; it does not defeat a control the site put up on purpose.&lt;/p&gt;

&lt;h2&gt;
  
  
  Provenance is a security property
&lt;/h2&gt;

&lt;p&gt;Answer quality is the obvious reason to care, but not the sharpest one. Agents act on what they read, which makes silent fetch failure an attack surface as well as a bug. If I can make your agent's fetch quietly fail, or serve it a decoy, I can steer what it does next. Retrieval suppression, cloaking, and context poisoning are all attacks on an agent that trusts a 200.&lt;/p&gt;

&lt;p&gt;That is why every decision ships with a receipt: the final URL after redirects, the status, raw bytes versus extracted bytes, and a content hash. A receipt is only as strong as what it proves, so it comes with two honest limits. The hash pins down what was read, which makes a later claim re-checkable, but it is not tamper-evidence; a cloaked page hashes cleanly. And provenance is not authenticity: the gate closes the fail-silent-on-empty hole, not the case where a fetch succeeds on wrong content that slips past the fingerprint. It raises the bar from "no evidence" to "evidence that passed the checks". That is worth having, and it is not everything.&lt;/p&gt;

&lt;p&gt;There is a third limit: a read is not an answer. RETRIEVED means enough content arrived, not that it contained the fact you needed. Checking that is groundedness work, and it composes downstream, after the gate has done its one job.&lt;/p&gt;

&lt;h2&gt;
  
  
  The pattern underneath
&lt;/h2&gt;

&lt;p&gt;This is the third time I have built the same shape. ReachGate decides whether a vulnerability is actually reachable in your code before a human touches the finding. TrustGate decides whether an agent may take a risky action given the live state of the data behind it. FetchGate decides whether a page was actually read before the agent answers from it.&lt;/p&gt;

&lt;p&gt;The domains differ, but the idea is the same: let a deterministic engine make the call, let the model only explain it, and ship every decision as proof you can check yourself.&lt;/p&gt;

&lt;p&gt;FetchGate is the smallest of the three, a gate that comes down to nearly one bit: read or not. But the fetch boundary is where an agent's grip on reality is thinnest, and it is the one place the other two do not cover. It is open source and deterministic, runs with no model and no API keys, and ships with a test that tries to sneak a fabricated read past the gate; the forgery gets caught, by design. A 200 is not a read, and an agent that cannot tell the difference will tell you it read a page it never saw, with a straight face, every time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Get it
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="s2"&gt;"fetchgate[mcp]"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/MoAz06/FetchGate" rel="noopener noreferrer"&gt;FetchGate on GitHub&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://pypi.org/project/fetchgate/" rel="noopener noreferrer"&gt;FetchGate on PyPI&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/MoAz06/FetchGate/releases" rel="noopener noreferrer"&gt;One-click Claude Desktop bundle&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It is open source and deterministic. If it saves you one silent bad answer, a star on the &lt;a href="https://github.com/MoAz06/FetchGate" rel="noopener noreferrer"&gt;repo&lt;/a&gt; helps others find it.&lt;/p&gt;

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