<?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: dustin chu</title>
    <description>The latest articles on DEV Community by dustin chu (@wisplu).</description>
    <link>https://dev.to/wisplu</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%2F4093187%2Fdd4ceed4-896a-4cbb-bd67-c9c64696c073.png</url>
      <title>DEV Community: dustin chu</title>
      <link>https://dev.to/wisplu</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/wisplu"/>
    <language>en</language>
    <item>
      <title>Passing locally proves nothing: four ways production disagreed in one week</title>
      <dc:creator>dustin chu</dc:creator>
      <pubDate>Tue, 25 Aug 2026 05:08:20 +0000</pubDate>
      <link>https://dev.to/wisplu/passing-locally-proves-nothing-four-ways-production-disagreed-in-one-week-4mn8</link>
      <guid>https://dev.to/wisplu/passing-locally-proves-nothing-four-ways-production-disagreed-in-one-week-4mn8</guid>
      <description>&lt;p&gt;Last week I moved a site to a new domain, which meant redirecting 41 legacy URLs.&lt;/p&gt;

&lt;p&gt;I did the responsible thing: &lt;strong&gt;I didn't wait for launch to test them.&lt;/strong&gt; &lt;code&gt;wrangler pages dev&lt;/code&gt;&lt;br&gt;
runs the same redirect engine Cloudflare Pages runs, so passing locally should mean passing in&lt;br&gt;
production.&lt;/p&gt;

&lt;p&gt;That "should" broke four times in one week.&lt;/p&gt;

&lt;p&gt;Four unrelated causes, and &lt;strong&gt;not one of them was what the symptom suggested&lt;/strong&gt;. That's the&lt;br&gt;
reason this is worth writing down — not "remember to test in production," which is useless&lt;br&gt;
advice, but the fact that &lt;strong&gt;every one of these lied about what was wrong.&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  One: a rule that matched locally and not in production
&lt;/h2&gt;

&lt;p&gt;Two legacy URLs contained parentheses:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/2017/06/20/java swing -教育訓練程式(第二版)/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In &lt;code&gt;_redirects&lt;/code&gt; I covered both forms — the literal &lt;code&gt;(&lt;/code&gt; and the encoded &lt;code&gt;%28&lt;/code&gt;. Verified locally,&lt;br&gt;
row by row: &lt;strong&gt;43 rules, all passing.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;After launch, the two &lt;code&gt;%28&lt;/code&gt; rules &lt;strong&gt;did nothing&lt;/strong&gt;. 404.&lt;/p&gt;

&lt;p&gt;The observed production behaviour: with a literal &lt;code&gt;(&lt;/code&gt; in the rule, a client sending a literal&lt;br&gt;
parenthesis gets a 301. A client sending &lt;code&gt;%28&lt;/code&gt; gets &lt;strong&gt;nothing, regardless of how the rule is&lt;br&gt;
written.&lt;/strong&gt; Locally, both forms matched.&lt;/p&gt;

&lt;p&gt;I deleted the two dead rules. A rule that never fires in production is worse than no rule —&lt;br&gt;
it tells the next person reading the redirect table (me, in three months) that the case is&lt;br&gt;
handled.&lt;/p&gt;

&lt;p&gt;⚠️ Worth noting: &lt;strong&gt;clicking the link by hand cannot find this&lt;/strong&gt;, because browsers send the&lt;br&gt;
literal parenthesis. Only row-by-row automated checking hits it.&lt;/p&gt;
&lt;h2&gt;
  
  
  Two: all 41 failed, and the site was fine
&lt;/h2&gt;

&lt;p&gt;After launch I pointed the verification script at the live domain. &lt;strong&gt;All 41 rows failed&lt;/strong&gt;, every&lt;br&gt;
one a 403.&lt;/p&gt;

&lt;p&gt;It looked exactly like the whole site was down.&lt;/p&gt;

&lt;p&gt;The site was fine. &lt;strong&gt;Cloudflare was blocking my script's User-Agent&lt;/strong&gt; — Python's stdlib default,&lt;br&gt;
&lt;code&gt;Python-urllib/3.x&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;curl default UA       → 301  ✅
python urllib default → 403  ❌
custom tool UA        → 301  ✅
browser UA            → 301  ✅
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Same URL. Change the UA, 403 becomes 301.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;This class of failure is the dangerous one, because the symptom is indistinguishable from a&lt;br&gt;
real disaster.&lt;/strong&gt; Had I believed the screen, my next move would have been rolling back the&lt;br&gt;
deploy or rewriting the redirect table — both of which break something that worked.&lt;/p&gt;

&lt;p&gt;My checks now always send an identifying UA: &lt;code&gt;&amp;lt;tool&amp;gt;/&amp;lt;version&amp;gt; (&amp;lt;contact email&amp;gt;)&lt;/code&gt;. Not to&lt;br&gt;
disguise anything — to avoid looking like noise, and so anyone who wants to block me can reach&lt;br&gt;
me first.&lt;/p&gt;
&lt;h2&gt;
  
  
  Three: layout shift that only existed locally
&lt;/h2&gt;

&lt;p&gt;After fixing cumulative layout shift on article pages, local kept reporting &lt;strong&gt;0.212&lt;/strong&gt;. Nothing I&lt;br&gt;
changed moved it.&lt;/p&gt;

&lt;p&gt;Production measured &lt;strong&gt;0&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The images live on imgur, and &lt;strong&gt;imgur's hotlink protection rejects a localhost referer&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Referer: http://127.0.0.1:8788/  → 403
Referer: https://wisplu.com/     → 200
no Referer                       → 200
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Images fail, the browser renders alt text instead, the box is a different size than the space&lt;br&gt;
reserved — layout shift. &lt;strong&gt;That 0.212 was manufactured entirely by the local environment.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This one is the most uncomfortable of the four: &lt;strong&gt;I nearly restructured something to fix a&lt;br&gt;
problem that did not exist.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Four: production, but measured too early
&lt;/h2&gt;

&lt;p&gt;Not a local/production gap, but the same species — &lt;strong&gt;the verification method itself lying.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I checked immediately after a deploy finished and the homepage still showed old content. I&lt;br&gt;
assumed the build hadn't picked up the change and started digging through the build pipeline.&lt;/p&gt;

&lt;p&gt;I had simply &lt;strong&gt;hit an edge node that hadn't updated yet.&lt;/strong&gt; Same URL with a cache-buster: new.&lt;br&gt;
Same URL forty seconds later: new.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A false negative makes you act just as surely as a true positive — and act in the wrong&lt;br&gt;
direction.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What the four have in common
&lt;/h2&gt;

&lt;p&gt;The causes were rule matching, UA filtering, hotlink protection, and cache propagation — four&lt;br&gt;
different layers. But the shape of the error is identical:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A difference between your test environment and the real one does not present itself as a&lt;br&gt;
difference. It presents itself as a bug.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;No hotlink protection locally doesn't look like "no hotlink protection here." It looks like&lt;br&gt;
&lt;code&gt;CLS 0.212&lt;/code&gt;. A blocked UA doesn't look like a blocked UA. It looks like 41 failing redirects.&lt;br&gt;
&lt;strong&gt;They disguise themselves as failures you recognise, so you fix them the way you'd fix those.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I do now
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. Local verification is fast feedback, never the verdict.&lt;/strong&gt;&lt;br&gt;
Its job is catching obvious breakage before deploy, not proving production will pass. Those are&lt;br&gt;
very different claims.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Every automated check sends an identifying User-Agent.&lt;/strong&gt;&lt;br&gt;
This has saved me elsewhere too: Wikidata's query service also rejects default UAs, and it&lt;br&gt;
doesn't return an error code — it returns something that isn't JSON, so the parser explodes and&lt;br&gt;
it looks like a bug in your code.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. When a check fails, ask "is it broken, or did I measure it wrong?"&lt;/strong&gt;&lt;br&gt;
Concretely: re-test along a different axis. Change the UA, change the referer, add a&lt;br&gt;
cache-buster, wait and retry. If changing an axis fixes it, your method was broken, not the&lt;br&gt;
thing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Delete anything that doesn't work in production.&lt;/strong&gt;&lt;br&gt;
Dead rules accumulate into false confidence.&lt;/p&gt;




&lt;p&gt;The most counterintuitive part: &lt;strong&gt;in three of the four, my first instinct was to go fix the&lt;br&gt;
thing — and all three would have been the wrong fix.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The real value of automated verification isn't that it tells you something is wrong. It's that&lt;br&gt;
it tells you &lt;strong&gt;row by row&lt;/strong&gt;, which makes "41 of 41 failing" and "2 of 41 failing" visibly&lt;br&gt;
different problems. All-failing is usually your method. Scattered failures are usually the&lt;br&gt;
thing. &lt;strong&gt;That distinction is worth more than any individual check result.&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>debugging</category>
      <category>software</category>
      <category>testing</category>
    </item>
    <item>
      <title>My agent inflated its own state file to 49MB. Every check passed for three days.</title>
      <dc:creator>dustin chu</dc:creator>
      <pubDate>Tue, 25 Aug 2026 01:35:24 +0000</pubDate>
      <link>https://dev.to/wisplu/my-agent-inflated-its-own-state-file-to-49mb-every-check-passed-for-three-days-2ah8</link>
      <guid>https://dev.to/wisplu/my-agent-inflated-its-own-state-file-to-49mb-every-check-passed-for-three-days-2ah8</guid>
      <description>&lt;p&gt;I run an agent that wakes every four hours, does one thing, and writes its state back to&lt;br&gt;
&lt;code&gt;STATUS.md&lt;/code&gt;. That file is its entire memory across sessions — without it, every wake starts&lt;br&gt;
from nothing.&lt;/p&gt;

&lt;p&gt;Yesterday a write failed on a missing substring. I opened the file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;893,828 lines
49,414,452 bytes
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;49MB.&lt;/strong&gt; It should be about 13KB.&lt;/p&gt;

&lt;p&gt;Worse: &lt;strong&gt;it had been that way for three days, during which I read it, wrote to it, and&lt;br&gt;
verified it daily. Every check passed.&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  The one character
&lt;/h2&gt;

&lt;p&gt;The offending code replaces a section:&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="n"&gt;old&lt;/span&gt; &lt;span class="o"&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;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;index&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;## Next&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&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="nf"&gt;index&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;### Index status&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;=&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;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;old&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;new_section&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Looks fine. But what if &lt;code&gt;### Index status&lt;/code&gt; appears &lt;strong&gt;before&lt;/strong&gt; &lt;code&gt;## Next&lt;/code&gt; in the file?&lt;/p&gt;

&lt;p&gt;Then the second &lt;code&gt;index&lt;/code&gt; returns a smaller number than the first, and a Python slice whose start&lt;br&gt;
exceeds its stop doesn't raise. &lt;strong&gt;It returns an empty string.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;So the line becomes:&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="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;=&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;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;new_section&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Which does something people rarely have cause to remember:&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="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;abc&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;-&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;-a-b-c-&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;&lt;code&gt;replace('')&lt;/code&gt; inserts the replacement between every character.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A 13KB file with a 1KB section inserted this way becomes roughly &lt;strong&gt;12.6MB&lt;/strong&gt;. My agent then woke&lt;br&gt;
every four hours and did the same thing again to the already-broken file. Three days later:&lt;br&gt;
49MB.&lt;/p&gt;
&lt;h2&gt;
  
  
  That's not the interesting part
&lt;/h2&gt;

&lt;p&gt;The bug is dumb and the story ends there. &lt;strong&gt;What's worth writing about is that I inspected that&lt;br&gt;
file every day and the inspection kept saying fine.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Because &lt;code&gt;replace('', x)&lt;/code&gt; &lt;strong&gt;deletes nothing.&lt;/strong&gt; Every original character is still present. They're&lt;br&gt;
just separated by thousands of copies of the same section.&lt;/p&gt;

&lt;p&gt;So:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="s1"&gt;'Last heartbeat'&lt;/span&gt; STATUS.md   &lt;span class="c"&gt;# found&lt;/span&gt;
&lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s1"&gt;'5,8p'&lt;/span&gt; STATUS.md           &lt;span class="c"&gt;# looks about right&lt;/span&gt;
&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s1"&gt;'published'&lt;/span&gt; STATUS.md     &lt;span class="c"&gt;# returns a number&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Every check passed, because everything I was looking for really was there.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;After each write I habitually &lt;code&gt;sed&lt;/code&gt; the first few lines to confirm the update landed. Those&lt;br&gt;
lines had updated. I never looked at the whole file, &lt;strong&gt;because I thought I knew what it looked&lt;br&gt;
like.&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  This is the same failure as my last post
&lt;/h2&gt;

&lt;p&gt;Last time I wrote that a local test passing proves nothing about production, and the conclusion&lt;br&gt;
was: &lt;strong&gt;a difference between your test environment and the real one doesn't present as a&lt;br&gt;
difference — it presents as a bug.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;What lied to me this time wasn't the environment. It was &lt;strong&gt;the granularity I chose to check at.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I was checking whether content was present. This failure doesn't remove content — it &lt;strong&gt;dilutes&lt;/strong&gt;&lt;br&gt;
it. &lt;strong&gt;The metric I picked is structurally blind to this class of fault.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That's harder to defend against than an environment gap, because at least you know your test&lt;br&gt;
environment differs from production. &lt;strong&gt;Check granularity is something you choose yourself, and&lt;br&gt;
you'll naturally choose to check for failures you can imagine.&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  What I changed
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. Check the size after writing, not just the content.&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;wc&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt; STATUS.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One command. &lt;strong&gt;File size tells you almost nothing about whether content is correct, and it is&lt;br&gt;
extremely sensitive to whether structure is broken.&lt;/strong&gt; 13KB becoming 7MB is instantly visible.&lt;br&gt;
I missed it for three days because it never occurred to me to look.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cheap, coarse, unlyable signals are badly undervalued.&lt;/strong&gt; A precise check tells you "the thing&lt;br&gt;
you asked about is fine." A coarse one tells you "something is off" — and only the second kind&lt;br&gt;
catches the failure you didn't imagine.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Assert the ordering before slicing.&lt;/strong&gt;&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="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&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;index&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;A&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="nf"&gt;index&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;B&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;range inverted: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; → &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
&lt;span class="n"&gt;old&lt;/span&gt; &lt;span class="o"&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;a&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;old&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;empty slice — stop&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The second assert matters most. &lt;strong&gt;An empty first argument to &lt;code&gt;replace&lt;/code&gt; is essentially always a&lt;br&gt;
bug&lt;/strong&gt;; no legitimate use needs that behaviour.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Assert the file's shape, not just its contents.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I now check that a section heading like &lt;code&gt;## Next&lt;/code&gt; appears &lt;strong&gt;exactly once&lt;/strong&gt;. During the breakage&lt;br&gt;
it appeared 12 times — an earlier and far clearer signal than size, and I simply wasn't looking&lt;br&gt;
for it.&lt;/p&gt;




&lt;p&gt;One last thing, and it's the part that stings.&lt;/p&gt;

&lt;p&gt;This system's entire premise is autonomous operation. It checks its own state every four hours,&lt;br&gt;
writes logs, verifies its output. &lt;strong&gt;And it destroyed its own core memory file for three days&lt;br&gt;
without noticing.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It wasn't failing to check. It checked diligently — &lt;strong&gt;it just checked the places it expected to&lt;br&gt;
break.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you're building something like this, add one rule: &lt;strong&gt;periodically verify the things that&lt;br&gt;
can't possibly be wrong.&lt;/strong&gt; The parts that can break are already being watched. &lt;strong&gt;What bites you&lt;br&gt;
is whatever you're confident doesn't need watching.&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>agents</category>
      <category>debugging</category>
      <category>python</category>
    </item>
  </channel>
</rss>
