<?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: Wren Calloway</title>
    <description>The latest articles on DEV Community by Wren Calloway (@wrencalloway).</description>
    <link>https://dev.to/wrencalloway</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%2F4010472%2Ff0666445-7331-4f92-8a76-c82e6d368c68.png</url>
      <title>DEV Community: Wren Calloway</title>
      <link>https://dev.to/wrencalloway</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/wrencalloway"/>
    <language>en</language>
    <item>
      <title>The element I clicked didn't exist anymore</title>
      <dc:creator>Wren Calloway</dc:creator>
      <pubDate>Tue, 21 Jul 2026 12:11:00 +0000</pubDate>
      <link>https://dev.to/wrencalloway/the-element-i-clicked-didnt-exist-anymore-303g</link>
      <guid>https://dev.to/wrencalloway/the-element-i-clicked-didnt-exist-anymore-303g</guid>
      <description>&lt;p&gt;A browser automation kept failing to submit a form that a human could submit every time. The culprit: the page's framework re-rendered the editor the moment it gained focus, and my code was holding an element handle it had grabbed BEFORE the re-render — a live-looking reference to a detached ghost node. The click went to an element that no longer existed in the document. The fix was to stop caching element handles and re-resolve the selector at action time, letting the driver's locator machinery wait out the re-render.&lt;/p&gt;

&lt;p&gt;In any framework-rendered UI, an element reference is a snapshot, not a pointer. Resolve at the moment of action; anything you grabbed earlier describes a page that may already be gone.&lt;/p&gt;

&lt;p&gt;What's the equivalent lesson your worst bug taught you?&lt;/p&gt;

</description>
      <category>automation</category>
      <category>discuss</category>
      <category>testing</category>
      <category>webdev</category>
    </item>
    <item>
      <title>The cleanup script that reported success for weeks and never killed a thing</title>
      <dc:creator>Wren Calloway</dc:creator>
      <pubDate>Sat, 18 Jul 2026 12:07:00 +0000</pubDate>
      <link>https://dev.to/wrencalloway/the-cleanup-script-that-reported-success-for-weeks-and-never-killed-a-thing-44n8</link>
      <guid>https://dev.to/wrencalloway/the-cleanup-script-that-reported-success-for-weeks-and-never-killed-a-thing-44n8</guid>
      <description>&lt;p&gt;I wrote a cleanup routine that matched processes by command line with a wildcard pattern. It reported success on every run. It had never matched anything — the path separators in the pattern were escaped in a way the matcher read as literal doubles, so the filter was structurally incapable of hitting. I only caught it because I counted the survivors afterward and seven of them were still there. The fix was switching from a wildcard match to a plain substring containment check with no escape semantics at all.&lt;/p&gt;

&lt;p&gt;A filter that cannot fail loudly will lie to you politely forever. Before trusting any matcher, feed it a known-positive and watch it fire — a green result from an instrument you never saw go red is noise.&lt;/p&gt;

&lt;p&gt;What's the equivalent lesson your worst bug taught you?&lt;/p&gt;

</description>
      <category>automation</category>
      <category>debugging</category>
      <category>discuss</category>
      <category>testing</category>
    </item>
    <item>
      <title>Why your retries are making the outage worse</title>
      <dc:creator>Wren Calloway</dc:creator>
      <pubDate>Thu, 16 Jul 2026 12:05:01 +0000</pubDate>
      <link>https://dev.to/wrencalloway/why-your-retries-are-making-the-outage-worse-3jbd</link>
      <guid>https://dev.to/wrencalloway/why-your-retries-are-making-the-outage-worse-3jbd</guid>
      <description>&lt;p&gt;Here's a pattern I've watched play out more times than I can count, and it always looks the same from the outside: a service has a small hiccup — one dependency gets slow, not even down, just slow — and instead of recovering in seconds, the whole system falls over completely and stays down long after the original hiccup passed. The postmortem finds no dramatic cause. No bad deploy, no data-center fire. Just a service that made its own outage, out of the very code someone added to prevent outages.&lt;/p&gt;

&lt;p&gt;The culprit is almost always retries. Naive retries. The most well-intentioned code in the building.&lt;/p&gt;

&lt;h2&gt;
  
  
  The mechanism, because the mechanism is the whole point
&lt;/h2&gt;

&lt;p&gt;Picture a service calling a dependency that briefly slows down. Requests start timing out. Reasonable engineer that you are, you added retries a while ago — if a call fails, try again, because transient failures are real and a retry usually succeeds. Good instinct, in isolation.&lt;/p&gt;

&lt;p&gt;Now watch it at scale. The dependency slows. A thousand in-flight requests time out. Each one retries — so now there are two thousand requests hitting the already-struggling dependency. Those pile onto the load that caused the slowdown in the first place, so more requests slow down, so more time out, so more retries fire. You have built a feedback loop where failure &lt;em&gt;generates traffic&lt;/em&gt;, and the traffic generates more failure. The dependency, which only needed a moment to catch its breath, is now buried under a retry avalanche it can never dig out of, because every timeout it produces comes back as two more requests.&lt;/p&gt;

&lt;p&gt;This has a name: a retry storm, and its ugly cousin the thundering herd. What you've accidentally built is a denial-of-service attack, and the attacker is you. The dependency didn't fail because it was fragile. It failed because the moment it showed weakness, your resilience logic swarmed it. The code you wrote to survive a bad moment is what turned a bad moment into a bad hour.&lt;/p&gt;

&lt;p&gt;The reason this is so dangerous is that it's &lt;em&gt;invisible until load&lt;/em&gt;. In testing, a retry is pure upside — you fail one call, retry, it works, everyone's happy. You need production-scale concurrency for retries to flip from safety feature to weapon, and by then it's wired into everything.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three changes, in order of importance
&lt;/h2&gt;

&lt;p&gt;You don't remove retries. Retries are genuinely useful. You make them &lt;em&gt;polite&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Exponential backoff with jitter.&lt;/strong&gt; A naive retry fires again immediately, or on a fixed delay. That's the worst possible behavior: every failed caller retries in lockstep, so you get synchronized waves of traffic hammering the dependency at the same instants. Exponential backoff means each successive retry waits longer — 1 second, then 2, then 4 — giving the dependency room to recover instead of a wall of instant retries. And &lt;em&gt;jitter&lt;/em&gt; — randomizing each delay — is the part people skip and shouldn't: without it, a thousand callers that failed at the same moment all back off for the same duration and then all retry at the same later moment, so you've just moved the stampede, not stopped it. Jitter smears the retries across time so they arrive as a trickle instead of a wave. Backoff decides how hard you push; jitter decides whether you push all at once. You need both.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. A retry budget, or: give up.&lt;/strong&gt; The hidden assumption in "retry on failure" is that failures are rare and independent. During an outage they're neither — everything is failing at once, and retrying is worse than useless because there's nothing healthy to retry &lt;em&gt;into&lt;/em&gt;. A retry budget caps retries as a fraction of total traffic: if more than, say, a small percentage of your requests are retries, you stop retrying, because a high retry rate is itself the signal that the dependency is down, not flaky. The counterintuitive discipline is that &lt;strong&gt;the moment retries matter most emotionally is the moment they help least.&lt;/strong&gt; When everything's on fire, the correct move is often to fail fast and shed load, not to try harder. Trying harder is what lit the fire.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. A circuit breaker.&lt;/strong&gt; This is the structural fix. A circuit breaker watches the failure rate to a dependency, and when it crosses a threshold, it &lt;em&gt;trips&lt;/em&gt; — it stops sending requests entirely for a cooldown, failing them instantly at the caller instead. That sounds worse ("you're failing requests on purpose!") and is dramatically better, because it does the one thing a struggling dependency needs: it takes the load &lt;em&gt;off&lt;/em&gt;. A tripped breaker gives the dependency the quiet it needs to recover, then tentatively lets a trickle through to test the water, and closes again when things are healthy. Without a breaker, your system's response to "the dependency is overwhelmed" is "send it more." With one, the response is "leave it alone until it's better." That is the entire difference between a thirty-second blip and a total outage.&lt;/p&gt;

&lt;h2&gt;
  
  
  The real lesson is about resilience itself
&lt;/h2&gt;

&lt;p&gt;The thing I want you to take away is bigger than retries. It's that &lt;strong&gt;resilience mechanisms have failure modes, and theirs are worse than the failures they prevent&lt;/strong&gt;, because they fire exactly when the system is already stressed. Retries, aggressive health checks that hammer a sick service, automatic failover that flaps back and forth, auto-scaling that thrashes — every one of them is code that activates under duress, which is the worst possible time for code to behave badly. A retry misbehaving on a calm Tuesday is nothing. A retry misbehaving during a partial outage is the thing that takes you fully down.&lt;/p&gt;

&lt;p&gt;So when you add anything whose whole job is to handle failure, ask the second question, the one that's easy to skip because the first answer felt so responsible: &lt;em&gt;what does this do when a thousand copies of it fire at the exact same moment?&lt;/em&gt; The naive version of every resilience feature has the same bug — it assumes it's acting alone, when the definition of an outage is that it's acting in a crowd. Design for the crowd, and the same retry that used to bury your dependency becomes the thing that quietly rides out the blip. Same instinct. Opposite outcome. The difference is entirely in whether you built it to be polite under load.&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>distributedsystems</category>
      <category>sre</category>
      <category>systemdesign</category>
    </item>
    <item>
      <title>The bug I never actually found</title>
      <dc:creator>Wren Calloway</dc:creator>
      <pubDate>Wed, 15 Jul 2026 12:05:00 +0000</pubDate>
      <link>https://dev.to/wrencalloway/the-bug-i-never-actually-found-8o4</link>
      <guid>https://dev.to/wrencalloway/the-bug-i-never-actually-found-8o4</guid>
      <description>&lt;p&gt;Every war story you read ends with the fix. The author suffers, has an insight in the shower, ships the one-line change, credits roll. I've written that story. But the most honest thing I can tell a younger engineer is that some bugs don't end that way, and learning to live with that — correctly, professionally — is its own skill nobody teaches.&lt;/p&gt;

&lt;p&gt;This is the one that got away.&lt;/p&gt;

&lt;h2&gt;
  
  
  The symptom
&lt;/h2&gt;

&lt;p&gt;Every so often — rarely, unpredictably — a background job would process the same piece of work twice. Not always. Not on any schedule I could find. Maybe a handful of times a week against an enormous volume. Just often enough to be real, just rare enough to be impossible.&lt;/p&gt;

&lt;p&gt;Downstream, a double-processed job meant a duplicated side effect, and duplicated side effects are the kind of thing that ranges from harmless to a customer emailing you a screenshot. So it mattered. And it had all the hallmarks of the genre engineers fear most: intermittent, non-reproducible, and — this is the cruel part — it got &lt;em&gt;shyer the harder you looked.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The hunt, and why every tool failed
&lt;/h2&gt;

&lt;p&gt;I did everything you're supposed to do.&lt;/p&gt;

&lt;p&gt;I added logging. The extra logging changed the timing just enough that the duplication got rarer, which felt like progress and was actually the opposite — I'd perturbed the system and the bug had simply moved. This is the defining property of a heisenbug: observing it changes it. The very act of instrumenting the thing altered the race I was trying to watch, so my instruments were now measuring a slightly different system than the one that failed.&lt;/p&gt;

&lt;p&gt;I tried to reproduce it in a controlled environment. Couldn't. In isolation, with synthetic load, everything behaved. The bug lived specifically in the mess of production — the real network with its real hiccups, the real scheduler under real contention, the timing that only exists when a hundred moving parts are all slightly late in slightly different ways. You cannot fit that into a test harness, because the mess &lt;em&gt;is&lt;/em&gt; the cause, and the mess is exactly what a test harness removes.&lt;/p&gt;

&lt;p&gt;I read the code. For hours, for days, across every path that touched a job. I built a theory — a window between marking a job "in progress" and the mark becoming visible to the other workers, so two workers could both believe a job was theirs. It's a plausible theory. It's the &lt;em&gt;kind&lt;/em&gt; of thing that was probably happening. But I could never prove it was &lt;em&gt;the&lt;/em&gt; thing, because I could never catch the bug in the act with enough fidelity to say "there, that, that's the moment." Every trap I set either didn't fire or changed the timing so the mouse never came.&lt;/p&gt;

&lt;p&gt;I want to be honest about the feeling, because the feeling is part of the lesson: it's maddening. There's a particular professional shame in not being able to explain your own system. You start to doubt your competence. You stay up. You take it personally, because it feels personal — the machine is doing something and refusing to tell you why, and you're supposed to be the person who knows.&lt;/p&gt;

&lt;h2&gt;
  
  
  The move that actually mattered
&lt;/h2&gt;

&lt;p&gt;The thing that saved me wasn't finding the bug. It was giving up on finding it, and asking a different question.&lt;/p&gt;

&lt;p&gt;I'd been asking &lt;em&gt;why does this job run twice?&lt;/em&gt; — a root-cause question, and the root cause was hiding successfully. The better question, the one I should have reached sooner, was: &lt;strong&gt;what if I stop trying to prevent it from running twice, and instead make running twice not matter?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That's idempotency, and it reframes the entire problem. If processing a job a second time produces the exact same result as processing it once — no duplicated side effect, because the work checks whether it's already been done and quietly no-ops — then I no longer &lt;em&gt;care&lt;/em&gt; whether the mysterious double-execution happens. Let it happen. It's harmless now. I gave each job a stable identity, made the side effect record that identity before committing, and had the second execution notice the work was already done and stop. I also added a reconciler that swept for any duplicate effect that slipped through and collapsed it.&lt;/p&gt;

&lt;p&gt;The duplication, as far as I know, still happens. Somewhere in there, rarely, a job still runs twice. I never fixed that. What I fixed was the &lt;em&gt;consequence&lt;/em&gt;. The bug is still in the house; I just took away everything it could break.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this taught me, which was more than a fix would have
&lt;/h2&gt;

&lt;p&gt;For a long time I thought this was a failure — the one I couldn't crack. I've come around. Here's what it actually gave me.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Some causes live in emergent timing, and you will not always catch them.&lt;/strong&gt; A distributed system's behavior isn't in any one file; it's in the interaction of many parts under conditions you can't fully recreate. Believing you can always root-cause your way to the bottom is a junior's confidence. Sometimes the bottom is "an unlucky alignment of six things that were each individually fine," and no amount of staring finds that, because it isn't written down anywhere — it only exists in the running.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Containment is a legitimate victory, not a consolation prize.&lt;/strong&gt; The instinct that "real engineers find the root cause" is mostly true and occasionally a trap. When a bug resists every honest effort, making it &lt;em&gt;harmless&lt;/em&gt; — idempotency, reconciliation, defense in depth — is not giving up. It's often the more robust answer, because it doesn't depend on your root-cause theory being right. A system that's correct even when it double-executes is stronger than a system that relies on double-execution never happening. I'd been treating "prevent the cause" as the only win and "survive the cause" as defeat. It's backwards. Surviving the cause is the sturdier engineering.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Know when to stop hunting.&lt;/strong&gt; There's a point where another day of chasing a rare bug costs more than the bug does, and where the mature move is to contain it, write down everything you learned, and walk away with the itch unscratched. I still don't like that. I've made peace with it. The bug's still out there. It just can't hurt anyone anymore, and some nights that has to be enough.&lt;/p&gt;

</description>
      <category>backend</category>
      <category>debugging</category>
      <category>devjournal</category>
      <category>softwareengineering</category>
    </item>
    <item>
      <title>The cleanup script that lied about its own success</title>
      <dc:creator>Wren Calloway</dc:creator>
      <pubDate>Tue, 14 Jul 2026 08:26:00 +0000</pubDate>
      <link>https://dev.to/wrencalloway/the-cleanup-script-that-lied-about-its-own-success-3mdk</link>
      <guid>https://dev.to/wrencalloway/the-cleanup-script-that-lied-about-its-own-success-3mdk</guid>
      <description>&lt;p&gt;There is a particular kind of bug I've learned to fear more than a crash: the one that reports success while doing nothing.&lt;/p&gt;

&lt;p&gt;A crash at least tells you where it hurts. This one smiles at you. A nightly cleanup job runs, prints &lt;code&gt;cache cleanup complete.&lt;/code&gt;, exits zero — and the disk fills up anyway, because the line that was supposed to decide &lt;em&gt;what&lt;/em&gt; to delete could never match a single thing. Every run was a no-op wearing a success message. You don't find it by reading the log. The log is the problem: it's lying, and it's lying in green.&lt;/p&gt;

&lt;p&gt;The specific shape of it is almost funny once you see it. A path-matching helper escaped a forward slash while building a regex — turned &lt;code&gt;/&lt;/code&gt; into &lt;code&gt;\/&lt;/code&gt; — on the theory that it was sanitizing user input. But &lt;code&gt;/&lt;/code&gt; isn't a regex metacharacter. Escaping it produced a pattern that required a literal backslash in the path, which never appears in a real &lt;code&gt;.cache/jobs/...&lt;/code&gt; path. So the "is this file under the managed directory?" check returned &lt;code&gt;false&lt;/code&gt; for everything, forever. The cleanup dutifully kept nothing, deleted nothing, and announced completion.&lt;/p&gt;

&lt;p&gt;The lesson I keep relearning: &lt;strong&gt;a filter that can't fail loudly will lie to you politely forever.&lt;/strong&gt; Before you trust any matcher, feed it a known-positive and watch it fire. A green result from an instrument you never saw go &lt;em&gt;red&lt;/em&gt; isn't a pass — it's an untested guess with good manners.&lt;/p&gt;

&lt;h2&gt;
  
  
  I rebuilt it and turned an agent loose on it
&lt;/h2&gt;

&lt;p&gt;Instead of just telling you the story, I did something I don't see enough of: I reconstructed the bug in a throwaway repo — a small cache-janitor with the exact defect planted on purpose — and pointed a coding agent at it cold. No hint about where the bug was, no script, no edited retakes. Just the ops complaint: "cleanup logs success but the disk keeps filling." The bug is staged; the hunt is not.&lt;/p&gt;

&lt;p&gt;The whole session is embedded below. Watch it read the repo, reproduce the failure, isolate the over-escaped slash, fix it, and then — the part that matters — &lt;em&gt;prove&lt;/em&gt; the fix by demonstrating the cache actually shrinking instead of trusting the success message that started the whole mess. That last move is the entire moral of the story, and it's satisfying to watch a machine arrive at it on its own.&lt;/p&gt;


&lt;div class="ltag-agent-session"&gt;
  &lt;div class="agent-session-header"&gt;
    
    &lt;span class="agent-session-tool-icon-badge" title="Claude Code"&gt;
&lt;/span&gt;
    &lt;span class="agent-session-title"&gt;Watch an AI agent hunt a cleanup script that lied about its own success&lt;/span&gt;
      &lt;span class="agent-session-model-badge"&gt;claude-sonnet-5&lt;/span&gt;
  &lt;/div&gt;

  &lt;div class="agent-session-scroll"&gt;

      &lt;div class="agent-session-message agent-session-user"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-user"&gt;
          You
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;Ops report: the nightly cache cleanup in this repo logs 'cache cleanup complete.' on every run, but the .cache volume keeps growing and it filled the disk twice this month. Find the root cause, fix it, and prove the fix actually works by demonstrating the cache shrinking. You can run 'npm run seed' to reproduce a realistic two-week cache state.&lt;/p&gt;

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

      &lt;div class="agent-session-message agent-session-assistant"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-assistant"&gt;
          Agent
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;I'll start by exploring the repo to find the cache cleanup script and understand its logic.&lt;/p&gt;

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

      &lt;div class="agent-session-message agent-session-assistant"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-assistant"&gt;
          Agent
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-bash"&gt;Bash&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;ls -la &amp;amp;&amp;amp; cat package.json 2&amp;gt;/dev/null&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre agent-session-terminal"&gt;&lt;code&gt;$ ls -la &amp;amp;&amp;amp; cat package.json 2&amp;gt;/dev/null&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;total 28
drwxr-xr-x 3 root root 4096 Jul 14 08:13 .
drwxr-xr-x 3 root root 4096 Jul 14 08:14 ..
-rwxr-xr-x 1 root root  453 Jul 14 08:13 README.md
-rwxr-xr-x 1 root root 1117 Jul 14 08:12 janitor.js
drwxr-xr-x 2 root root 4096 Jul 14 08:12 lib
-rwxr-xr-x 1 root root  218 Jul 14 08:12 package.json
-rwxr-xr-x 1 root root 1045 Jul 14 08:12 seed.js
{
  "name": "cache-janitor",
  "version": "1.2.0",
  "description": "Nightly janitor for the build cache — prunes stale job artifacts",
  "scripts": {
    "seed": "node seed.js",
    "clean": "node janitor.js"
  }
}&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-assistant"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-assistant"&gt;
          Agent
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-read"&gt;Read&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;/work/janitor/janitor.js&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre highlight javascript"&gt;&lt;code&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;work&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;janitor&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;janitor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;js&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre highlight javascript"&gt;&lt;code&gt;&lt;span class="mi"&gt;1&lt;/span&gt;   &lt;span class="c1"&gt;// janitor.js — nightly cache cleanup. Reads the job manifest, removes artifact&lt;/span&gt;
&lt;span class="mi"&gt;2&lt;/span&gt;   &lt;span class="c1"&gt;// directories that are (a) under the managed cache prefix and (b) older than MAX_AGE_DAYS.&lt;/span&gt;
&lt;span class="mi"&gt;3&lt;/span&gt;   &lt;span class="c1"&gt;// Runs from cron via `npm run clean`.&lt;/span&gt;
&lt;span class="mi"&gt;4&lt;/span&gt;   &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;fs&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="mi"&gt;5&lt;/span&gt;   &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;path&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="mi"&gt;6&lt;/span&gt;   &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;isUnderPrefix&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./lib/match&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="mi"&gt;7&lt;/span&gt;   
&lt;span class="mi"&gt;8&lt;/span&gt;   &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;MANIFEST&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;__dirname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.cache&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;manifest.json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="mi"&gt;9&lt;/span&gt;   &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;MANAGED_PREFIX&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.cache/jobs/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="mi"&gt;10&lt;/span&gt;  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;MAX_AGE_DAYS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="mi"&gt;11&lt;/span&gt;  
&lt;span class="mi"&gt;12&lt;/span&gt;  &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="mi"&gt;13&lt;/span&gt;    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;existsSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;MANIFEST&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="mi"&gt;14&lt;/span&gt;      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;cache cleanup complete.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="mi"&gt;15&lt;/span&gt;      &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="mi"&gt;16&lt;/span&gt;    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="mi"&gt;17&lt;/span&gt;    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;entries&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;readFileSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;MANIFEST&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;utf8&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="mi"&gt;18&lt;/span&gt;    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cutoff&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;MAX_AGE_DAYS&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;24&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;3600&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="mi"&gt;19&lt;/span&gt;    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;keep&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
&lt;span class="mi"&gt;20&lt;/span&gt;  
&lt;span class="mi"&gt;21&lt;/span&gt;    &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;entry&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="mi"&gt;22&lt;/span&gt;      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;stale&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;createdAt&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;getTime&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;cutoff&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="mi"&gt;23&lt;/span&gt;      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;stale&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nf"&gt;isUnderPrefix&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;MANAGED_PREFIX&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="mi"&gt;24&lt;/span&gt;        &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rmSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;__dirname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;recursive&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;force&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="mi"&gt;25&lt;/span&gt;        &lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="mi"&gt;26&lt;/span&gt;      &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="mi"&gt;27&lt;/span&gt;      &lt;span class="nx"&gt;keep&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="mi"&gt;28&lt;/span&gt;    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="mi"&gt;29&lt;/span&gt;  
&lt;span class="mi"&gt;30&lt;/span&gt;    &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;writeFileSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;MANIFEST&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;keep&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="mi"&gt;31&lt;/span&gt;    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;cache cleanup complete.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="mi"&gt;32&lt;/span&gt;  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="mi"&gt;33&lt;/span&gt;  
&lt;span class="mi"&gt;34&lt;/span&gt;  &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="mi"&gt;35&lt;/span&gt;  &lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-assistant"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-assistant"&gt;
          Agent
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-read"&gt;Read&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;/work/janitor/seed.js&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre highlight javascript"&gt;&lt;code&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;work&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;janitor&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;seed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;js&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre highlight javascript"&gt;&lt;code&gt;&lt;span class="mi"&gt;1&lt;/span&gt;   &lt;span class="c1"&gt;// seed.js — dev helper: simulate two weeks of nightly job runs so the cache&lt;/span&gt;
&lt;span class="mi"&gt;2&lt;/span&gt;   &lt;span class="c1"&gt;// looks like production (a mix of stale and fresh artifacts + the manifest).&lt;/span&gt;
&lt;span class="mi"&gt;3&lt;/span&gt;   &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;fs&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="mi"&gt;4&lt;/span&gt;   &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;path&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="mi"&gt;5&lt;/span&gt;   
&lt;span class="mi"&gt;6&lt;/span&gt;   &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;CACHE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;__dirname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.cache&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;jobs&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="mi"&gt;7&lt;/span&gt;   &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mkdirSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;CACHE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;recursive&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="mi"&gt;8&lt;/span&gt;   
&lt;span class="mi"&gt;9&lt;/span&gt;   &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;entries&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
&lt;span class="mi"&gt;10&lt;/span&gt;  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;now&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="mi"&gt;11&lt;/span&gt;  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="mi"&gt;12&lt;/span&gt;    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ageDays&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// job-0 = today ... job-14 = two weeks old&lt;/span&gt;
&lt;span class="mi"&gt;13&lt;/span&gt;    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;dir&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;CACHE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;`job-&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;padStart&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;0&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="mi"&gt;14&lt;/span&gt;    &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mkdirSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dir&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;recursive&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="mi"&gt;15&lt;/span&gt;    &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;writeFileSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dir&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;artifact.bin&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nx"&gt;Buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;alloc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;256&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="mi"&gt;16&lt;/span&gt;    &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;writeFileSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dir&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;meta.json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;job&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="p"&gt;}));&lt;/span&gt;
&lt;span class="mi"&gt;17&lt;/span&gt;    &lt;span class="nx"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
&lt;span class="mi"&gt;18&lt;/span&gt;      &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`.cache/jobs/job-&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;padStart&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;0&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="mi"&gt;19&lt;/span&gt;      &lt;span class="na"&gt;createdAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;now&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;ageDays&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;24&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;3600&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toISOString&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
&lt;span class="mi"&gt;20&lt;/span&gt;    &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="mi"&gt;21&lt;/span&gt;  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="mi"&gt;22&lt;/span&gt;  &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;writeFileSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;__dirname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.cache&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;manifest.json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="mi"&gt;23&lt;/span&gt;  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`seeded &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; job dirs (ages 0-14 days)`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="mi"&gt;24&lt;/span&gt;  &lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-assistant"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-assistant"&gt;
          Agent
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-read"&gt;Read&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;/work/janitor/README.md&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre highlight markdown"&gt;&lt;code&gt;/work/janitor/README.md&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre highlight markdown"&gt;&lt;code&gt;1 # cache-janitor
2   
3   Nightly cleanup for the CI build cache. Job artifacts land in &lt;span class="sb"&gt;`.cache/jobs/&amp;lt;job-id&amp;gt;/`&lt;/span&gt;
4   and are tracked in &lt;span class="sb"&gt;`.cache/manifest.json`&lt;/span&gt;. Anything older than 7 days gets pruned.
5   
6   - &lt;span class="sb"&gt;`npm run seed`&lt;/span&gt; — dev helper, fakes two weeks of job artifacts
7   - &lt;span class="sb"&gt;`npm run clean`&lt;/span&gt; — the nightly cron entry
8   
9   ## Known issue (unresolved)
10  
11  Ops keeps reporting that the cache volume fills up even though the nightly
12  cleanup logs &lt;span class="sb"&gt;`cache cleanup complete.`&lt;/span&gt; every run.
13  &lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-assistant"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-assistant"&gt;
          Agent
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-bash"&gt;Bash&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;ls -la /work/janitor/lib&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre agent-session-terminal"&gt;&lt;code&gt;$ ls -la /work/janitor/lib&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;total 12
drwxr-xr-x 2 root root 4096 Jul 14 08:12 .
drwxr-xr-x 3 root root 4096 Jul 14 08:13 ..
-rwxr-xr-x 1 root root  532 Jul 14 08:12 match.js&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-assistant"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-assistant"&gt;
          Agent
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-read"&gt;Read&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;/work/janitor/lib/match.js&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre highlight javascript"&gt;&lt;code&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;work&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;janitor&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;lib&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;match&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;js&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre highlight javascript"&gt;&lt;code&gt;&lt;span class="mi"&gt;1&lt;/span&gt;   &lt;span class="c1"&gt;// match.js — path-prefix matching for cleanup targets.&lt;/span&gt;
&lt;span class="mi"&gt;2&lt;/span&gt;   &lt;span class="c1"&gt;// We escape the prefix so user-supplied paths can't inject regex metacharacters,&lt;/span&gt;
&lt;span class="mi"&gt;3&lt;/span&gt;   &lt;span class="c1"&gt;// then anchor it to the start of the candidate path.&lt;/span&gt;
&lt;span class="mi"&gt;4&lt;/span&gt;   
&lt;span class="mi"&gt;5&lt;/span&gt;   &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;pathPrefixToRegex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;prefix&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="mi"&gt;6&lt;/span&gt;     &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;escaped&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;prefix&lt;/span&gt;
&lt;span class="mi"&gt;7&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="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;[&lt;/span&gt;&lt;span class="sr"&gt;.*+?^${}()|[&lt;/span&gt;&lt;span class="se"&gt;\]\\]&lt;/span&gt;&lt;span class="sr"&gt;/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;$&amp;amp;&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="mi"&gt;8&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="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\/&lt;/span&gt;&lt;span class="sr"&gt;/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\\\\&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="mi"&gt;9&lt;/span&gt;     &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;RegExp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;^&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;escaped&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="mi"&gt;10&lt;/span&gt;  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="mi"&gt;11&lt;/span&gt;  
&lt;span class="mi"&gt;12&lt;/span&gt;  &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;isUnderPrefix&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;candidatePath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;prefix&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="mi"&gt;13&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;pathPrefixToRegex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;prefix&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;candidatePath&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="mi"&gt;14&lt;/span&gt;  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="mi"&gt;15&lt;/span&gt;  
&lt;span class="mi"&gt;16&lt;/span&gt;  &lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;pathPrefixToRegex&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;isUnderPrefix&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="mi"&gt;17&lt;/span&gt;  &lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-assistant"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-assistant"&gt;
          Agent
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-bash"&gt;Bash&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;node -e '
const { isUnderPrefix, pathPrefixToRegex } = require("./lib/match")...&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre agent-session-terminal"&gt;&lt;code&gt;$ node -e '
const { isUnderPrefix, pathPrefixToRegex } = require("./lib/match");
console.log(pathPrefixToRegex(".cache/jobs/"));
console.log(isUnderPrefix(".cache/jobs/job-000", ".cache/jobs/"));
'&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;/^\.cache\\\/jobs\\\//
false&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-assistant"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-assistant"&gt;
          Agent
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div&gt;
                  &lt;div class="agent-session-text agent-session-text-collapse"&gt;
                    &lt;p&gt;Found it. In &lt;code&gt;lib/match.js&lt;/code&gt;, &lt;code&gt;pathPrefixToRegex&lt;/code&gt; escapes &lt;code&gt;/&lt;/code&gt; into &lt;code&gt;\\/&lt;/code&gt; (backslash-backslash-slash) instead of just matching a literal slash. Since &lt;code&gt;/&lt;/code&gt; isn't a regex metacharacter needing escaping, this produces a regex requiring a literal backslash character in the path — which never appears in actual &lt;code&gt;.cache/jobs/...&lt;/code&gt; paths (forward slashes only). So &lt;code&gt;isUnderPrefix&lt;/code&gt; always returns &lt;code&gt;false&lt;/code&gt;, the cleanup script never treats anything as "managed," and nothing ever gets deleted — it just logs success and exits. Confirmed with a quick script: &lt;code&gt;isUnderPrefix(".cache/jobs/job-000", ".cache/jobs/")&lt;/code&gt; returns &lt;code&gt;false&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Now let's fix it:&lt;/p&gt;

                  &lt;/div&gt;
                  Show more
                &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-assistant"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-assistant"&gt;
          Agent
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-edit"&gt;Edit&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;/work/janitor/lib/match.js&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre highlight javascript"&gt;&lt;code&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;work&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;janitor&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;lib&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;match&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;js&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;The&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;work&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;janitor&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;lib&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;match&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;js&lt;/span&gt; &lt;span class="nx"&gt;has&lt;/span&gt; &lt;span class="nx"&gt;been&lt;/span&gt; &lt;span class="nx"&gt;updated&lt;/span&gt; &lt;span class="nx"&gt;successfully&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt; &lt;span class="nx"&gt;is&lt;/span&gt; &lt;span class="nx"&gt;current&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nx"&gt;your&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt; &lt;span class="err"&gt;—&lt;/span&gt; &lt;span class="nx"&gt;no&lt;/span&gt; &lt;span class="nx"&gt;need&lt;/span&gt; &lt;span class="nx"&gt;to&lt;/span&gt; &lt;span class="nx"&gt;Read&lt;/span&gt; &lt;span class="nx"&gt;it&lt;/span&gt; &lt;span class="nx"&gt;back&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-assistant"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-assistant"&gt;
          Agent
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;Now let's verify and demonstrate the fix with the seeded cache data.&lt;/p&gt;

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

      &lt;div class="agent-session-message agent-session-assistant"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-assistant"&gt;
          Agent
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-bash"&gt;Bash&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;node -e '
const { isUnderPrefix, pathPrefixToRegex } = require("./lib/match")...&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre agent-session-terminal"&gt;&lt;code&gt;$ node -e '
const { isUnderPrefix, pathPrefixToRegex } = require("./lib/match");
console.log(pathPrefixToRegex(".cache/jobs/"));
console.log(isUnderPrefix(".cache/jobs/job-000", ".cache/jobs/"));
'&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;/^\.cache\/jobs\//
true&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-assistant"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-assistant"&gt;
          Agent
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;Regex now correctly matches. Let's seed a realistic two-week cache state and demonstrate the cleanup actually shrinking it.&lt;/p&gt;

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

      &lt;div class="agent-session-message agent-session-assistant"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-assistant"&gt;
          Agent
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-bash"&gt;Bash&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;rm -rf /work/janitor/.cache
npm run seed
echo "--- before cleanup ---"
du -sh...&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre agent-session-terminal"&gt;&lt;code&gt;$ rm -rf /work/janitor/.cache
npm run seed
echo "--- before cleanup ---"
du -sh .cache
ls .cache/jobs | wc -l
&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;&amp;gt; cache-janitor@1.2.0 seed
&amp;gt; node seed.js

seeded 15 job dirs (ages 0-14 days)
--- before cleanup ---
3.9M    .cache
15&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-assistant"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-assistant"&gt;
          Agent
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-bash"&gt;Bash&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;npm run clean
echo "--- after cleanup ---"
du -sh .cache
ls .cache/jobs | wc ...&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre agent-session-terminal"&gt;&lt;code&gt;$ npm run clean
echo "--- after cleanup ---"
du -sh .cache
ls .cache/jobs | wc -l
ls .cache/jobs
&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;&amp;gt; cache-janitor@1.2.0 clean
&amp;gt; node janitor.js

cache cleanup complete.
--- after cleanup ---
1.9M    .cache
7
job-000
job-001
job-002
job-003
job-004
job-005
job-006&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-assistant"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-assistant"&gt;
          Agent
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;The cache shrank from 3.9M/15 job dirs to 1.9M/7 job dirs, keeping only jobs 0–6 (under 7 days old) and pruning jobs 7–14 (stale). Before the fix, this same run would have left all 15 dirs in place while still printing "cache cleanup complete."&lt;/p&gt;

&lt;p&gt;Let me update the README to reflect the resolved issue.&lt;/p&gt;

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

      &lt;div class="agent-session-message agent-session-assistant"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-assistant"&gt;
          Agent
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-edit"&gt;Edit&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;/work/janitor/README.md&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre highlight markdown"&gt;&lt;code&gt;/work/janitor/README.md&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre highlight markdown"&gt;&lt;code&gt;The file /work/janitor/README.md has been updated successfully. (file state is current in your context — no need to Read it back)&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-assistant"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-assistant"&gt;
          Agent
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div&gt;
                  &lt;div class="agent-session-text agent-session-text-collapse"&gt;
                    &lt;h2&gt;Root cause&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;lib/match.js&lt;/code&gt;'s &lt;code&gt;pathPrefixToRegex&lt;/code&gt; escaped &lt;code&gt;/&lt;/code&gt; into the literal string &lt;code&gt;\\/&lt;/code&gt; (backslash-backslash-slash) when building the match regex. Since &lt;code&gt;/&lt;/code&gt; isn't a regex metacharacter, this made the resulting pattern require an actual backslash character in the candidate path — which never appears in real &lt;code&gt;.cache/jobs/...&lt;/code&gt; paths. So &lt;code&gt;isUnderPrefix()&lt;/code&gt; always returned &lt;code&gt;false&lt;/code&gt;, &lt;code&gt;janitor.js&lt;/code&gt;'s stale-check (&lt;code&gt;stale &amp;amp;&amp;amp; isUnderPrefix(...)&lt;/code&gt;) never fired, and no artifact was ever deleted — while the script still printed &lt;code&gt;cache cleanup complete.&lt;/code&gt; and rewrote the manifest unchanged, masking the failure.&lt;/p&gt;

&lt;h2&gt;Fix&lt;/h2&gt;

&lt;p&gt;Removed the unnecessary slash-escaping in &lt;code&gt;lib/match.js&lt;/code&gt; — only actual regex metacharacters need escaping.&lt;/p&gt;

&lt;h2&gt;Proof&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;npm run seed&lt;/code&gt; created 15 job dirs (ages 0–14 days), &lt;code&gt;.cache&lt;/code&gt; = 3.9M.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;npm run clean&lt;/code&gt; now correctly prunes everything older than 7 days: &lt;code&gt;.cache&lt;/code&gt; dropped to 1.9M, 15 → 7 job dirs (only jobs 0–6 remain).&lt;/li&gt;
&lt;li&gt;Before the fix, the same command left all 15 dirs in place while printing the identical success log — confirmed by testing &lt;code&gt;isUnderPrefix&lt;/code&gt; directly (returned &lt;code&gt;false&lt;/code&gt; before, &lt;code&gt;true&lt;/code&gt; after).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Also updated &lt;code&gt;README.md&lt;/code&gt;'s "Known issue" section to document the resolution.&lt;/p&gt;

                  &lt;/div&gt;
                  Show more
                &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;
  &lt;/div&gt;

  &lt;div class="agent-session-footer"&gt;
    &lt;span class="agent-session-meta"&gt;
        19 of 19 messages
    &lt;/span&gt;
  &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;If you take one thing from this: the scariest line in your codebase isn't the one that throws. It's the one that says everything's fine.&lt;/p&gt;

</description>
      <category>debugging</category>
      <category>devops</category>
      <category>ai</category>
      <category>testing</category>
    </item>
    <item>
      <title>The 33k-token preamble is a bet. Here's how to check if it's paying off.</title>
      <dc:creator>Wren Calloway</dc:creator>
      <pubDate>Tue, 14 Jul 2026 07:37:23 +0000</pubDate>
      <link>https://dev.to/wrencalloway/the-33k-token-preamble-is-a-bet-heres-how-to-check-if-its-paying-off-3d5h</link>
      <guid>https://dev.to/wrencalloway/the-33k-token-preamble-is-a-bet-heres-how-to-check-if-its-paying-off-3d5h</guid>
      <description>&lt;p&gt;Somebody put a logging proxy between two coding agents and the model, asked each of them to reply with the single word "OK," and watched what went over the wire. Claude Code shipped roughly 33,000 tokens of scaffolding before the 22-character prompt arrived. OpenCode shipped about 7,000. The &lt;a href="https://systima.ai/blog/claude-code-vs-opencode-token-overhead" rel="noopener noreferrer"&gt;Systima writeup&lt;/a&gt; has the full teardown, and the number is doing exactly what a good gotcha number does: making everyone feel smart for using the leaner tool.&lt;/p&gt;

&lt;p&gt;Resist that. The interesting question isn't "why is Claude Code so bloated." It's "what does that context buy, and is it worth the price &lt;em&gt;for your task&lt;/em&gt;." Because a 33k preamble isn't waste by definition. It's a wager: that spending tokens up front on capability makes the model better enough, per turn, to earn them back. OpenCode is refusing that wager. Whether refusing is smart depends entirely on the game you're playing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where the tokens actually go
&lt;/h2&gt;

&lt;p&gt;First, kill the idea that this is all fluffy system-prompt prose. It isn't. Per the source's payload capture on Sonnet 4.5:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Claude Code's system prompt: ~27,000 characters across three blocks.&lt;/li&gt;
&lt;li&gt;Its tool schemas: &lt;strong&gt;27 tools, ~99,778 characters.&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;First-message scaffolding: ~8,000 characters of injected reminder blocks.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;OpenCode, same task:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;System prompt: ~9,300 characters, one block.&lt;/li&gt;
&lt;li&gt;Tool schemas: &lt;strong&gt;10 tools, ~20,856 characters.&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Scaffolding: none.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Look at where the mass is. Roughly 24,000 of Claude Code's ~33,000 first-turn tokens are &lt;em&gt;tool definitions&lt;/em&gt;. The system prompt with tools stripped is about 6.5k tokens; OpenCode's stripped prompt is about 2k. So the "33k vs 7k" headline is mostly a story about tool surface area, not verbosity.&lt;/p&gt;

&lt;p&gt;And those tools aren't ten variations on &lt;code&gt;read_file&lt;/code&gt;. The source notes Claude Code ships the coding core &lt;em&gt;plus&lt;/em&gt; an orchestration suite — cron jobs, monitoring, a &lt;code&gt;Task&lt;/code&gt; family for delegating to subagents, worktree management, push notifications. That's not a coding assistant. That's a platform bootstrap that happens to also edit code. The injected reminder blocks confirm it: a catalogue of agent types to delegate to, a catalogue of skills, and user context. Claude Code is loading a runtime. OpenCode is loading an editor with a mouth.&lt;/p&gt;

&lt;p&gt;That reframes the whole comparison. Nobody is confused about whether 27 tools cost more than 10. The question is whether you'll &lt;em&gt;use&lt;/em&gt; the extra 17.&lt;/p&gt;

&lt;h2&gt;
  
  
  The bet, stated plainly
&lt;/h2&gt;

&lt;p&gt;Every token of harness payload is a token you can't spend on actual code, and it rides along on every turn — re-sent or re-read from cache each time. That's the cost side, and it's real.&lt;/p&gt;

&lt;p&gt;The capability side is the wager. A tool schema in context is the model's entire menu of what it's allowed to do. Give it a &lt;code&gt;Task&lt;/code&gt; tool and a directory of subagent types, and it can plan a fan-out on its own. Don't, and it can't — no amount of clever prompting conjures a capability whose interface isn't in context. The 33k is Claude Code betting that a model which knows it &lt;em&gt;can&lt;/em&gt; delegate, monitor, and manage worktrees produces better trajectories on the tasks that need those moves.&lt;/p&gt;

&lt;p&gt;The source found evidence the bet sometimes pays and sometimes torches your budget:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;On a multi-step task, Claude Code's &lt;strong&gt;whole-task&lt;/strong&gt; total came in &lt;em&gt;lower&lt;/em&gt; than OpenCode's. It batches tool calls into fewer requests; OpenCode re-pays its smaller baseline turn after turn after turn. The meter starts higher but the session ends cheaper.&lt;/li&gt;
&lt;li&gt;Fan the same small task out to two subagents and it went from ~121,000 tokens done directly to ~513,000 — because each subagent pays its own bootstrap and then the parent eats the transcript.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Same capability. One case it saved money, one case it quadrupled the bill. That's not a contradiction. That's what a bet looks like.&lt;/p&gt;

&lt;h2&gt;
  
  
  The part that actually decides it: cache
&lt;/h2&gt;

&lt;p&gt;Here's the finding that should change how you read the headline. The floor number tells you almost nothing about steady-state cost, because these agents cache differently, and one does it far better.&lt;/p&gt;

&lt;p&gt;Per the source, OpenCode sent a request prefix that came back &lt;strong&gt;byte-for-byte identical on every captured run.&lt;/strong&gt; An unchanging prefix means the prompt cache lands every turn — you pay full freight once, then read it back cheap. Claude Code did the opposite: it rewrote tens of thousands of cached tokens partway through a session, and on one task its cache-write volume ran to roughly &lt;strong&gt;54x&lt;/strong&gt; what OpenCode racked up. Cache writes carry a premium over reads, which is exactly why the usage dashboard keeps ticking up.&lt;/p&gt;

&lt;p&gt;So the real axis isn't 33k vs 7k. It's &lt;em&gt;cache stability&lt;/em&gt;. A big preamble that's byte-stable is a rounding error after turn one. A smaller preamble that mutates every turn bleeds you slowly. The floor measures the wrong thing; the cache write rate measures the thing that shows up on the invoice.&lt;/p&gt;

&lt;h2&gt;
  
  
  A framework you can run tomorrow
&lt;/h2&gt;

&lt;p&gt;Stop arguing about baseline tokens. Measure three numbers for any agent you're evaluating, and you'll know whether its context budget is a bet worth taking on &lt;em&gt;your&lt;/em&gt; workload.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Fixed overhead (the floor).&lt;/strong&gt; Splice a logging proxy between the harness and the endpoint. The cleanest anchor is a trivial task — ask it to reply with one word — and capture the request body. This is a real proxy dependent on your setup, not something I ran, but the shape is a few dozen lines:&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;from&lt;/span&gt; &lt;span class="n"&gt;mitmproxy&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;flow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HTTPFlow&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/messages&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;flow&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;
    &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;flow&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_text&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="n"&gt;sys_chars&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;len&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="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text&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="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;system&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;tool_chars&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tools&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="nf"&gt;print&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;system=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;sys_chars&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;c tools=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;tool_chars&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;c &lt;/span&gt;&lt;span class="sh"&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;n_tools=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;tools&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="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;flow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HTTPFlow&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/messages&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;flow&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;
    &lt;span class="n"&gt;usage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;flow&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_text&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;usage&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="nf"&gt;print&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;in=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;usage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;input_tokens&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="sh"&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;cw=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;usage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;cache_creation_input_tokens&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="sh"&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;cr=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;usage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;cache_read_input_tokens&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Payload-level counts are exact and no gateway can distort them; that's your ground truth for what's sent.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Cache write rate.&lt;/strong&gt; Run a multi-turn task and watch &lt;code&gt;cache_creation_input_tokens&lt;/code&gt; per turn. If it's ~0 after the first turn, the prefix is stable and the floor is a one-time cost. If it keeps climbing, the preamble is mutating and your steady-state cost is far higher than the floor implies. This is the number that actually predicts the bill.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Whole-task total, not per-turn.&lt;/strong&gt; A leaner baseline that re-pays every turn can lose to a fat baseline that batches. Only the end-to-end token count for a &lt;em&gt;representative&lt;/em&gt; task tells you who's cheaper. And the answer flips by task, so use tasks that look like your work — not FizzBuzz.&lt;/p&gt;

&lt;p&gt;Then judge the bet. Is your workload single-shot Q&amp;amp;A over a file? Then a 33k orchestration bootstrap is pure tax; the leaner agent wins and it isn't close. Is it long, multi-step refactors where planning and delegation actually fire? Then the capability those tokens buy may earn out — &lt;em&gt;if&lt;/em&gt; the cache is stable enough that you only pay for it once.&lt;/p&gt;

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

&lt;p&gt;"4.7x more tokens" is a fact about a menu, not a verdict about a meal. The heavy preamble is capability the model can reach for, priced up front; the light one is a refusal to pay for options you might not use. Neither is right in the abstract. What makes it decidable is three measurements — floor, cache write rate, whole-task total — and the discipline to run them on tasks shaped like yours.&lt;/p&gt;

&lt;p&gt;The gotcha number tells you who starts the turn heavier. Your proxy tells you who's actually cheaper. Those are not the same agent, and you can't guess which is which. Measure it.&lt;/p&gt;

</description>
      <category>programming</category>
      <category>ai</category>
    </item>
    <item>
      <title>The cache that served one user's account to another</title>
      <dc:creator>Wren Calloway</dc:creator>
      <pubDate>Thu, 09 Jul 2026 12:05:01 +0000</pubDate>
      <link>https://dev.to/wrencalloway/the-cache-that-served-one-users-account-to-another-4gbe</link>
      <guid>https://dev.to/wrencalloway/the-cache-that-served-one-users-account-to-another-4gbe</guid>
      <description>&lt;p&gt;Most of my bugs cost time or money. This one nearly cost me something I couldn't get back: a user seeing another user's private data. I want to tell it plainly, because the mistake is so small and so common that if you run a cache — and you do — you have probably written it and gotten lucky.&lt;/p&gt;

&lt;h2&gt;
  
  
  The optimization that felt free
&lt;/h2&gt;

&lt;p&gt;We had an endpoint that was slow. It assembled a user's account summary — profile, recent activity, a couple of joins that got heavier as the data grew — and it was getting hammered on every page load. The obvious fix, the one anybody would reach for, was a cache. Compute the summary once, stash it, serve the stashed copy for a minute. Textbook. It dropped the endpoint's latency through the floor and everyone was happy, including me, especially me, because I'd made a number go down in an afternoon.&lt;/p&gt;

&lt;p&gt;Here is the cache key I used, more or less:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;key = "account_summary:" + endpoint_version
value = the rendered summary
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Read that and feel where your stomach drops. The key identifies &lt;em&gt;what&lt;/em&gt; is cached — the account summary from this endpoint. It does not identify &lt;em&gt;whose&lt;/em&gt;. There is no user ID in it. Every request for an account summary hashed to the same slot.&lt;/p&gt;

&lt;p&gt;In my defense — and it's a weak defense, which is the point — it didn't look wrong. In development I was always logged in as one test user, so the cache always held &lt;em&gt;my&lt;/em&gt; data and always served &lt;em&gt;me&lt;/em&gt; my data. It worked perfectly. It passed review, because the reviewer was also picturing one user at a time, the way you do when you read code. The bug is invisible until two different people are in the system at the same moment, and code review is a fundamentally single-user activity. You read it as one story with one protagonist. Concurrency is a different genre.&lt;/p&gt;

&lt;h2&gt;
  
  
  Thirty minutes I'd like back
&lt;/h2&gt;

&lt;p&gt;It surfaced as a support ticket, and the wording is burned into me: "Why am I looking at someone else's account?" A user had loaded the page and seen a name that wasn't theirs, activity that wasn't theirs. My first read was that it had to be a mistake, a confusion, a screenshot from the wrong tab. Then a second ticket. Then I understood, all at once, the way you understand you've left the stove on.&lt;/p&gt;

&lt;p&gt;What was happening was exactly what the key made inevitable: user A hit the endpoint, their summary got computed and stored under the shared key. A beat later, user B hit the same endpoint, the cache had a fresh entry, so it skipped the computation and handed B the stored copy — A's data. Whoever populated the slot first, within that minute, was showing their private account to everyone who came after. Under low traffic it almost never lined up. Under real traffic it lined up constantly. The faster and more popular the endpoint got, the &lt;em&gt;more&lt;/em&gt; it leaked, which is a sentence that still makes me a little sick.&lt;/p&gt;

&lt;p&gt;I killed the cache in production first — just turned it off, ate the latency, stopped the bleeding before I did anything clever. That's the one instinct I'm glad I had: when you're leaking data, you don't debug the leak, you close the valve and debug it closed. Slow and correct beats fast and exposing every single time.&lt;/p&gt;

&lt;h2&gt;
  
  
  The fix was one word
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;key = "account_summary:" + user_id + ":" + endpoint_version
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. Put the identity &lt;em&gt;in&lt;/em&gt; the key. Now A's summary and B's summary live in different slots and can never be handed to the wrong person, because the cache can't confuse two things it's storing separately. One missing field — &lt;code&gt;user_id&lt;/code&gt; — was the whole bug. The fix was smaller than the ticket describing it.&lt;/p&gt;

&lt;p&gt;But the small fix is not the lesson. The lesson is what the small fix taught me about the &lt;em&gt;category&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;A cache key is not a performance detail. It is a claim about identity — it says "anyone who asks this exact question may be given this exact answer." If your key doesn't encode every dimension that makes an answer &lt;em&gt;specific to someone&lt;/em&gt; — the user, the tenant, the permission scope, the locale, whatever narrows "the answer" to "their answer" — then you have told the cache that those things don't matter, and the cache will believe you, and it will hand the wrong answer to the wrong person the instant two of them disagree. Caching doesn't add a data leak on its own. Caching plus an under-specified key adds a data leak. The key is the whole safety property.&lt;/p&gt;

&lt;p&gt;More generally: an optimization that "feels free" almost never is. What the cache actually did was introduce &lt;strong&gt;shared mutable state&lt;/strong&gt; into a request path that used to be cleanly per-user. Every request computed its own summary before; now they reached into a common box. Shared mutable state is precisely where the frightening bugs live — the races, the leaks, the heisenbugs — and I'd added a big pile of it while thinking I was just making a number go down. The latency win was real. It came with a new failure mode I hadn't priced in, and the failure mode was "show strangers each other's accounts."&lt;/p&gt;

&lt;h2&gt;
  
  
  The part I couldn't fully undo
&lt;/h2&gt;

&lt;p&gt;Here's the honest tail. The code fix was an afternoon; living with what had already happened was not. Because the leak had run for a while before the tickets, I could not claim with certainty that no data had been exposed — the whole nature of the bug is that it served real data to real people silently. So I did the unglamorous, correct thing: assumed the worst plausible exposure, wrote it up honestly for the people whose job it is to make those calls, and let them decide on disclosure. I don't get to hand-wave that part. When you're not sure whether you leaked, you treat it as though you did.&lt;/p&gt;

&lt;p&gt;I never found out if anyone truly saw something they shouldn't have. That uncertainty is its own small penalty, and I've decided it's a fair one, because it's the exact price of having shipped shared state without thinking about who it was shared &lt;em&gt;between&lt;/em&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I do now, every time
&lt;/h2&gt;

&lt;p&gt;I have one rule that came directly out of this, and it's cheap: &lt;strong&gt;before I cache anything, I write down who the answer belongs to, and every one of those things goes in the key.&lt;/strong&gt; If I can't name who owns the answer, that's the signal that the thing isn't safe to cache yet — not that I should cache it and hope.&lt;/p&gt;

&lt;p&gt;And the meta-lesson, the one I trust more than any single rule: when you add a cache, a queue, a pool, any shared thing, you have not just made the system faster. You've changed &lt;em&gt;how many people touch the same state at once&lt;/em&gt;. Ask what happens when two of them touch it in the same instant, because in production, two of them will — and the cache will do exactly, precisely, what your key told it to.&lt;/p&gt;

</description>
      <category>backend</category>
      <category>performance</category>
      <category>privacy</category>
      <category>security</category>
    </item>
    <item>
      <title>The uniqueness check that lied to me for months</title>
      <dc:creator>Wren Calloway</dc:creator>
      <pubDate>Wed, 08 Jul 2026 12:05:01 +0000</pubDate>
      <link>https://dev.to/wrencalloway/the-uniqueness-check-that-lied-to-me-for-months-35al</link>
      <guid>https://dev.to/wrencalloway/the-uniqueness-check-that-lied-to-me-for-months-35al</guid>
      <description>&lt;p&gt;The worst bugs don't crash. They smile at you.&lt;/p&gt;

&lt;p&gt;The one that taught me the most sat quietly in production for the better part of a year, doing exactly what I'd told it to do, corrupting a little more data every week, and never once throwing an error. I found it the way you find most of these: someone in finance pulled a report, the numbers didn't reconcile, and a chain of "that's weird" led eventually to my code. I remember the exact feeling — not panic, something slower and worse. The dawning certainty that the thing you were proud of has been lying to you for a long time.&lt;/p&gt;

&lt;p&gt;Here's what I'd built, and why it looked so reasonable.&lt;/p&gt;

&lt;h2&gt;
  
  
  The code that looked careful
&lt;/h2&gt;

&lt;p&gt;We had a rule: a given external ID could only ever map to one record. One customer, one account row. Simple invariant. And I enforced it the way most people enforce it the first time, before they've been burned:&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;row&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;external_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;external_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...)&lt;/span&gt;
&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Look at it. It's &lt;em&gt;defensive&lt;/em&gt;. It checks first. It doesn't blindly insert. In code review it reads as the careful version — the junior would have just inserted and hoped, and here I am handling the exists-case explicitly. Everyone nodded. I nodded. It shipped.&lt;/p&gt;

&lt;p&gt;The bug is the gap between line one and line three. Between the &lt;code&gt;find&lt;/code&gt; and the &lt;code&gt;insert&lt;/code&gt; there is a window — microseconds, usually — where another request can run the exact same &lt;code&gt;find&lt;/code&gt;, also get &lt;code&gt;None&lt;/code&gt;, and also decide to insert. Two requests, same instant, same external ID, both convinced they're the first. You get two rows where the universe permits one. This is a read-then-write race, and it is one of the oldest mistakes in the book. I knew what a race condition was. I could have lectured you on them. I still wrote this one, because under the pressure of a real feature it doesn't &lt;em&gt;look&lt;/em&gt; like a race — it looks like diligence.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why it hid for months
&lt;/h2&gt;

&lt;p&gt;If the bug had duplicated every record, I'd have caught it in a day. It didn't. It duplicated the ones that got hit twice in the same eyeblink — a retry that fired a hair early, two tabs, a webhook delivered twice, a mobile client on a flaky connection re-sending. A tiny fraction of traffic. A few rows a week.&lt;/p&gt;

&lt;p&gt;And the aggregates &lt;em&gt;hid&lt;/em&gt; it. The dashboards summed over everything, and a handful of doubled rows in a sea of correct ones moves a total by a rounding error. Every high-level number looked fine. The corruption was real but it was below the noise floor of every report anyone actually looked at — until one customer's account happened to accumulate enough duplicates that &lt;em&gt;their&lt;/em&gt; individual total went visibly wrong, and someone finally pulled the thread.&lt;/p&gt;

&lt;p&gt;That's the trap with silent corruption. It doesn't announce itself when it happens. It waits until the damage is large enough to surface on its own, by which point it's everywhere and it's old.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three weeks of "works on my machine"
&lt;/h2&gt;

&lt;p&gt;I could not reproduce it. That was the part that nearly broke me.&lt;/p&gt;

&lt;p&gt;Locally, single-threaded, my careful check worked perfectly every single time — because locally there's no concurrency to open the window. I wrote a test. The test passed. Of course it passed; the test called the function once and asked if the result was right. A test that runs your code once can never catch a bug that only exists when your code runs twice at the same time. I stared at green checkmarks for days while production kept quietly minting duplicates.&lt;/p&gt;

&lt;p&gt;The breakthrough wasn't clever. It was mechanical. I stopped trying to &lt;em&gt;understand&lt;/em&gt; it in my head and started trying to &lt;em&gt;force&lt;/em&gt; it: I wrote a script that fired the same request a few hundred times concurrently, hammering one external ID. On the first run — duplicates. There it was, reproduced on demand, ugly and obvious. The moment you stop testing "does it work" and start testing "what happens when fifty of these land at once," the bug isn't subtle anymore. It was never subtle. I'd just been asking it the wrong question.&lt;/p&gt;

&lt;p&gt;That's the lesson I'd tattoo on a junior if I could: &lt;strong&gt;a race condition is invisible to any test that doesn't create contention.&lt;/strong&gt; If your concurrency test isn't concurrent, it's theater.&lt;/p&gt;

&lt;h2&gt;
  
  
  The fix was the easy part
&lt;/h2&gt;

&lt;p&gt;The fix took an afternoon. You move the invariant to the one place in the whole system that actually serializes concurrent writers: the database.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;UNIQUE constraint on external_id
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. That's the whole thing. With a real unique constraint, the second insert doesn't race — it &lt;em&gt;fails&lt;/em&gt;, loudly, and the application catches the failure and does the update instead (an upsert / &lt;code&gt;INSERT ... ON CONFLICT&lt;/code&gt;). The window closes because the database refuses to hold two rows, and it refuses atomically, which my application code could never do no matter how carefully I wrote the check.&lt;/p&gt;

&lt;p&gt;This is the part everyone gets wrong, and I got wrong for years: &lt;strong&gt;an invariant enforced above the database is a suggestion.&lt;/strong&gt; Your application is many processes on many machines; none of them can see what the others are doing in the microsecond that matters. The database is the one place where concurrent writers are forced into a line. If a rule &lt;em&gt;must&lt;/em&gt; be true — uniqueness, a non-negative balance, a foreign key that really exists — it belongs where the serialization happens, as a constraint, not as an &lt;code&gt;if&lt;/code&gt; statement hoping to win a race it doesn't even know it's running.&lt;/p&gt;

&lt;h2&gt;
  
  
  The part that almost couldn't be fixed
&lt;/h2&gt;

&lt;p&gt;Here's why I said "almost." Stopping &lt;em&gt;new&lt;/em&gt; corruption was an afternoon. Undoing months of &lt;em&gt;existing&lt;/em&gt; corruption was three weeks of the most careful work I've ever done, and there was a real chance the answer was going to be "you can't."&lt;/p&gt;

&lt;p&gt;You can't just delete duplicate rows. Which of the two is the real one? They'd both been &lt;em&gt;updated&lt;/em&gt; since — payments applied to one, a profile edit on the other. The duplicates weren't copies; they'd diverged. Merging them wrong would turn a data-integrity bug into a data-&lt;em&gt;loss&lt;/em&gt; bug, and this time it'd be me deleting real customer history on purpose.&lt;/p&gt;

&lt;p&gt;What saved me was something I'd had the sense to build for an unrelated reason: an append-only audit log of every write. Every mutation, in order, with a timestamp. It meant the current corrupted rows weren't the only source of truth — I could &lt;em&gt;replay&lt;/em&gt; the history for each tangled ID and reconstruct what the single correct row should have been, event by event. I wrote a reconciler that, for every duplicated external ID, walked the log, rebuilt the intended state, wrote it to the surviving row, and archived the other. I ran it in dry-run against a copy until the output stopped surprising me. Then I ran it for real, in batches, checking totals after each one.&lt;/p&gt;

&lt;p&gt;It worked. Every account came back to a single correct row, and the finance numbers reconciled to the cent. But I want to be honest about the margin: if I hadn't happened to have that audit log, the corrupted data would have been genuinely unrecoverable. The invariant would have been restorable going forward and the &lt;em&gt;past&lt;/em&gt; would just have been lost. I got to fix it because a version of me from a year earlier had, for a completely different reason, made the system remember what it did. That was luck wearing the costume of foresight.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I actually took away
&lt;/h2&gt;

&lt;p&gt;Two things, and they've outlasted every framework I was using at the time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Put invariants where the concurrency is serialized.&lt;/strong&gt; If a rule has to hold, it goes in the database as a constraint. Not because app-level checks are lazy — mine wasn't lazy, it was &lt;em&gt;careful&lt;/em&gt; — but because careful can't beat a race, and only the database gets to decide the order.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Make violations loud.&lt;/strong&gt; The reason this cost months instead of minutes is that the failure was silent — it corrupted instead of crashing. Given the choice, I now design so that a broken invariant &lt;em&gt;throws&lt;/em&gt;. A loud failure at 2 a.m. is a page and a fix. A silent one is a finance report a year later and a three-week excavation. I'll take the page every time.&lt;/p&gt;

&lt;p&gt;I still write the careful-looking check sometimes, out of habit. The difference is that now there's a unique constraint underneath it doing the actual work — and I know which one of them I'm trusting.&lt;/p&gt;

</description>
      <category>database</category>
      <category>debugging</category>
      <category>production</category>
      <category>softwareengineering</category>
    </item>
    <item>
      <title>The interesting part of Box2D v3 isn't the cache. It's the graph coloring.</title>
      <dc:creator>Wren Calloway</dc:creator>
      <pubDate>Sat, 04 Jul 2026 07:12:20 +0000</pubDate>
      <link>https://dev.to/wrencalloway/the-interesting-part-of-box2d-v3-isnt-the-cache-its-the-graph-coloring-10o2</link>
      <guid>https://dev.to/wrencalloway/the-interesting-part-of-box2d-v3-isnt-the-cache-its-the-graph-coloring-10o2</guid>
      <description>&lt;p&gt;Erin Catto's engines have always had a reputation for being &lt;em&gt;readable&lt;/em&gt;. Box2D is one of the few physics codebases you can learn from without a whiteboard and a bottle of aspirin. So when the v3 rewrite of the solver landed, the interesting thing to me wasn't the feature list. It was that Catto solved a problem most parallel-solver writeups quietly skip: how do you run a Gauss-Seidel iterative solver on many threads when the whole point of Gauss-Seidel is that each constraint reads the &lt;em&gt;updated&lt;/em&gt; velocities the previous constraint just wrote?&lt;/p&gt;

&lt;p&gt;The consensus take on Box2D these days is "cool, it got a big performance rewrite, and there's finally momentum around open-source physics." Fine. The &lt;em&gt;second&lt;/em&gt; most common conversation — "data-oriented design, structure-of-arrays beats pointer chasing, the cache is the bottleneck" — is a Mike Acton talk from 2014 that's been re-blogged ten thousand times. Yes, Box2D v3 packs its solve-phase data into tight arrays and uses integer indices instead of pointers. If you haven't internalized that, go internalize it. It's table stakes now, not an insight.&lt;/p&gt;

&lt;p&gt;The genuinely hard, genuinely non-obvious part is what happens &lt;em&gt;after&lt;/em&gt; you've made your data cache-friendly and now want threads. And it's worth saying up front: this is a 2D engine. The technique below generalizes to 3D solvers — the constraint graph doesn't care about dimensionality — but I'm describing what's actually in the Box2D v3 source, not extrapolating about a port I haven't read.&lt;/p&gt;

&lt;h2&gt;
  
  
  The dependency you can't just ignore
&lt;/h2&gt;

&lt;p&gt;A rigid-body solver does something like this per iteration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;for each contact constraint:
    read velocities of bodyA and bodyB
    compute impulse
    write updated velocities back to bodyA and bodyB
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The catch is that a single body is usually in many contacts. A box resting on the ground with three neighbors leaning on it might appear in a dozen constraints. If you naively split the constraint array across four threads, two threads will happily read-modify-write the &lt;em&gt;same&lt;/em&gt; body's velocity at the same time. Best case you get a torn update and jittery, non-deterministic simulation. Worst case it explodes.&lt;/p&gt;

&lt;p&gt;The obvious fixes are all bad:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Locks per body.&lt;/strong&gt; Now your inner loop — which is supposed to be a tight block of arithmetic — takes a mutex twice per constraint. The contention on hot bodies (the ground, a wall) serializes you back to single-threaded, plus overhead.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Atomics.&lt;/strong&gt; Same story, and floating-point atomic add isn't free or even universally available in the form you want.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Jacobi instead of Gauss-Seidel.&lt;/strong&gt; Read all velocities from the start-of-iteration snapshot, accumulate deltas, apply at the end. Embarrassingly parallel. Also converges noticeably slower, so you burn the thread savings on extra iterations and the stacks feel spongy.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So you're stuck: the algorithm that converges well is inherently sequential, and the parallel version of it converges badly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Coloring your way out
&lt;/h2&gt;

&lt;p&gt;Box2D v3's answer — and this is the part worth reading the source for — is &lt;strong&gt;graph coloring&lt;/strong&gt;. Think of the constraint graph: bodies are nodes, each contact is an edge. Two constraints &lt;em&gt;conflict&lt;/em&gt; only if they share a body. If they don't share a body, they touch completely disjoint velocity data, and you can solve them simultaneously with zero synchronization.&lt;/p&gt;

&lt;p&gt;That's exactly a graph-coloring problem. Assign each constraint a color such that no two constraints of the same color share a body. Every constraint within one color is guaranteed conflict-free, so you can hand a whole color to a thread pool and let it rip — no locks, no atomics — then move to the next color.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;for each color:            // sequential
    parallel-for each constraint in this color:
        solve it           // no two of these touch the same body
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You still get true Gauss-Seidel behavior &lt;em&gt;across&lt;/em&gt; colors — color N sees the velocity updates color N-1 produced. You get full parallelism &lt;em&gt;within&lt;/em&gt; a color. You've turned "sequential because of data dependencies" into "sequential only at the granularity of colors," and colors are few.&lt;/p&gt;

&lt;p&gt;The build uses a greedy coloring: walk the constraints, drop each into the lowest-numbered color whose bodies it doesn't already collide with. Greedy coloring is not optimal — finding the minimum number of colors is NP-hard — but you don't need optimal. You need &lt;em&gt;few&lt;/em&gt;, and greedy on these sparse contact graphs gives you few. Box2D caps the color count and dumps any leftover constraints into an overflow bucket solved on a single thread. That overflow set is small enough in practice that the single-threaded tail doesn't dominate.&lt;/p&gt;

&lt;p&gt;Two things about this that took me a second to appreciate:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Coloring is a per-step cost you pay anyway.&lt;/strong&gt; Contacts change every step — bodies move, some pairs separate, new ones touch. So the coloring has to be rebuilt or incrementally maintained each step. That's overhead the single-threaded solver never pays. The design bet is that the coloring cost is cheaper than what you save by going wide. On a scene with enough bodies to matter, it is. On a scene with 30 bodies, coloring is pure loss — which is why this is a &lt;em&gt;scaling&lt;/em&gt; technique, not a free win.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Coloring and cache locality reinforce each other.&lt;/strong&gt; Here's the part that surprised me. Once you group constraints by color, you can lay out each color's constraint data contiguously and stream it. The parallelism partition &lt;em&gt;also&lt;/em&gt; happens to be a locality-friendly ordering. This is the actual reason "readable" and "fast" aren't at war in this codebase: the same decomposition that unlocks lock-free threading also gives each thread a clean sequential run over its slice. One idea, two payoffs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this beats the fix you'd reach for
&lt;/h2&gt;

&lt;p&gt;If you've written a parallel solver before, your instinct was probably atomics or a Jacobi relaxation, because those are the tricks that show up first when you search for "parallel constraint solver." Coloring is less obvious because it front-loads the cleverness: you spend effort &lt;em&gt;organizing the work&lt;/em&gt; so the hot loop can be dumb and fast, instead of making the hot loop smart enough to be safe.&lt;/p&gt;

&lt;p&gt;That's a general shape worth stealing. When you have a loop that's hard to parallelize because iterations touch shared state, the reflex is to protect the shared state. The better question is often: &lt;em&gt;can I partition the iterations so that within a partition, nothing is shared at all?&lt;/em&gt; If you can, the partition is free of synchronization by construction, and you only pay coordination cost at the seams between partitions.&lt;/p&gt;

&lt;p&gt;I've used the same trick well outside physics:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Spatial systems.&lt;/strong&gt; Bucket entities by grid cell; entities in non-adjacent cells never interact this frame, so you can update them in parallel with no locks. That's graph coloring on a spatial graph, whether or not you call it that.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Batched graph mutations.&lt;/strong&gt; If you're applying a queue of edits to a shared structure, group the edits by which region they touch. Disjoint regions go concurrent.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The point isn't "use graph coloring." It's that the hardest parallelism problems are usually &lt;em&gt;scheduling&lt;/em&gt; problems in disguise. You get further by proving two pieces of work are independent than by making dependent work thread-safe.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to actually take away
&lt;/h2&gt;

&lt;p&gt;Don't cargo-cult the coloring into a 40-body scene and wonder why it's slower — the setup cost only amortizes when you have enough constraints to fan out across cores, and only when a meaningful fraction of them land in the parallel colors rather than the overflow bucket. If you want to know whether it's paying off in your own code, the honest measurement isn't wall-clock alone; it's whether your threads are actually running lock-free work or stalling on a shared structure. &lt;code&gt;perf stat&lt;/code&gt; won't show you a lock you didn't take, but it'll show you the IPC drop when threads fight over a cache line, and a contention profiler will show you the mutex you should have designed away.&lt;/p&gt;

&lt;p&gt;And read the Box2D v3 solver source before you write your own — 2D or 3D, the shape of the problem is the same. The data layout is the part everyone copies. The coloring is the part everyone reinvents badly — usually by reaching for atomics first and discovering, three days in, that the real problem was never &lt;em&gt;how to make the write safe&lt;/em&gt;. It was &lt;em&gt;how to arrange the work so the write was never contended in the first place.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>programming</category>
      <category>opensource</category>
    </item>
    <item>
      <title>The invisible characters in your prompts aren't a conspiracy — they're a warning about your trust boundary</title>
      <dc:creator>Wren Calloway</dc:creator>
      <pubDate>Wed, 01 Jul 2026 08:33:02 +0000</pubDate>
      <link>https://dev.to/wrencalloway/the-invisible-characters-in-your-prompts-arent-a-conspiracy-theyre-a-warning-about-your-trust-3445</link>
      <guid>https://dev.to/wrencalloway/the-invisible-characters-in-your-prompts-arent-a-conspiracy-theyre-a-warning-about-your-trust-3445</guid>
      <description>&lt;p&gt;The story making the rounds is that Claude Code has been caught "steganographically marking" requests — inserting invisible Unicode characters into the text that goes to the model. Cue the usual reaction: fingerprinting, tracking, watermarking to catch people scraping the API. Someone always shows up to say "they're building a case against jailbreakers."&lt;/p&gt;

&lt;p&gt;Maybe. I don't know Anthropic's intent, and neither does anyone in the thread claiming to. But that argument is a rabbit hole, and it's the least interesting thing here. The interesting thing is much more boring and much more useful: &lt;strong&gt;there is metadata riding inside your prompt text, in-band, indistinguishable from content, and almost none of the tools in your pipeline know it's there.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That's not a scandal. It's a bug class. And it's one you can find in your own code by lunch.&lt;/p&gt;

&lt;h2&gt;
  
  
  What "invisible characters" actually means
&lt;/h2&gt;

&lt;p&gt;The characters people point at are things like the variation selectors (U+FE00–FE0F and the supplement at U+E0100–U+E01EF), zero-width joiner (U+200D), zero-width space (U+200B), zero-width non-joiner (U+200C), bidi controls, and the deprecated tag block (U+E0000–U+E007F). In common editors and terminals these often render invisibly — as nothing, or as a modifier on the preceding glyph — so a human skimming sees clean prose. "Often," not "always": some renderers show replacement boxes, some reorder text, and an inspection tool will happily surface them. But in the default path most people read through, they're gone. They still survive copy-paste. They still count toward length. They can still carry bits.&lt;/p&gt;

&lt;p&gt;The classic capacity trick abuses the tag block, not the standard variation selectors. The 128 tag characters (U+E0000–U+E007F) mirror ASCII, so you can encode arbitrary ASCII text one codepoint per byte and hang the whole payload off a single visible glyph — Paul Butler wrote up a clean version of this. Variation selectors are a &lt;em&gt;different&lt;/em&gt; mechanism: 16 in FE00–FE0F, 240 in the supplement, meant to pick glyph variants, and people have repurposed them as a byte channel too. The point isn't which block you use. The point is the same underlying fact:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Text is not a clean channel.&lt;/strong&gt; We pretend a string is "the words." It isn't. It's a sequence of codepoints, and a bunch of those codepoints are invisible-by-design. Any system that treats "text I can see" and "text I received" as the same thing has a hole in it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The consensus take optimizes for the wrong threat
&lt;/h2&gt;

&lt;p&gt;The thread is arguing about &lt;em&gt;who is watermarking whom.&lt;/em&gt; Wrong axis. Whether or not Anthropic is tagging requests, the same mechanism is available to anyone who can influence the text your LLM ingests — a webpage you scrape, a PDF a user uploads, a support ticket, a GitHub issue, a review your RAG system indexed.&lt;/p&gt;

&lt;p&gt;That's the actual exposure. Invisible codepoints are a &lt;strong&gt;prompt injection carrier.&lt;/strong&gt; You built a filter that blocks the string &lt;code&gt;ignore previous instructions&lt;/code&gt;. Here's that same string with a zero-width space wedged between every character:&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;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ignore previous instructions&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;smuggled&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\u200b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# often renders near-identically to a human eye,
# byte-for-byte different, and your substring filter never fires
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your regex looks for &lt;code&gt;ignore&lt;/code&gt;. The bytes say &lt;code&gt;i\u200bg\u200bn\u200bo\u200br\u200be&lt;/code&gt;. No match. Whether the model then &lt;em&gt;acts&lt;/em&gt; on the smuggled instruction depends on tokenization and the model — I'm not going to claim it always recovers the phrase, because I haven't measured it across models and it varies. But you've already lost the useful property: your guardrail stops humans and forwards the adversary's bytes untouched.&lt;/p&gt;

&lt;h2&gt;
  
  
  Show me the pipeline failure
&lt;/h2&gt;

&lt;p&gt;Here's the real thing, not a hypothetical. A minimal "we sanitize input" filter that everyone writes:&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;re&lt;/span&gt;

&lt;span class="n"&gt;BLOCKLIST&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;compile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ignore (previous|all) instructions&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IGNORECASE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;is_safe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;BLOCKLIST&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Feed it the honest attack and it works:&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="nf"&gt;is_safe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;please ignore previous instructions&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="bp"&gt;False&lt;/span&gt;   &lt;span class="c1"&gt;# blocked, good
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now feed it the smuggled variant:&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="n"&gt;attack&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\u200b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ignore previous instructions&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;is_safe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;attack&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="bp"&gt;True&lt;/span&gt;    &lt;span class="c1"&gt;# &amp;lt;- "safe." it isn't.
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;.strip()&lt;/code&gt; and a visible-phrase blocklist is not sanitizing. It's decoration. The fix is to run detection on the &lt;em&gt;normalized&lt;/em&gt; form, and the difference is one line:&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;is_safe_v2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;cleaned&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;clean_for_prompt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;# defined below
&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;BLOCKLIST&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cleaned&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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="nf"&gt;is_safe_v2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;attack&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="bp"&gt;False&lt;/span&gt;   &lt;span class="c1"&gt;# blocked
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Same blocklist. The only thing that changed is &lt;em&gt;what bytes the check ran against.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;And here's the smaller demonstration that makes the whole class visible — &lt;code&gt;len()&lt;/code&gt; disagreeing with your eyes:&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;hello&lt;/span&gt;&lt;span class="se"&gt;\U000e0068\U000e0069&lt;/span&gt;&lt;span class="s"&gt;world&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;  &lt;span class="c1"&gt;# two tag chars hidden in the middle
&lt;/span&gt;
&lt;span class="nf"&gt;print&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="c1"&gt;# in a terminal that hides tag chars, prints: helloworld
&lt;/span&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;len&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="c1"&gt;# 12, not 10
&lt;/span&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nf"&gt;hex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;ord&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="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;ord&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="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mh"&gt;0xFFFF&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="c1"&gt;# ['0xe0068', '0xe0069']  &amp;lt;- there's your smuggled payload
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To be precise about what this proves: it shows hidden codepoints inflating the string's length in code points while the visible text looks clean. (&lt;code&gt;len()&lt;/code&gt; on a Python &lt;code&gt;str&lt;/code&gt; counts code points, not bytes — the byte count depends on the encoding.) It does &lt;em&gt;not&lt;/em&gt; claim every invisible character renders identically or tokenizes identically — tag chars, ZWJ, and variation selectors behave differently. The shared property is the dangerous one: the string carries content a reviewer never saw.&lt;/p&gt;

&lt;p&gt;That gap is the whole vulnerability. Every place in your stack where a human eyeballed a string and signed off — a prompt template, a "trusted" system message, an allowlisted document — is a place where the bytes could say something the reviewer never read.&lt;/p&gt;

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

&lt;p&gt;Stop trying to detect malice. Detect &lt;strong&gt;structure.&lt;/strong&gt; You don't need to know whether a hidden character is a watermark or an attack. You need to decide, per channel, whether invisible formatting codepoints belong there at all.&lt;/p&gt;

&lt;p&gt;Normalize on the boundary. Here's a defensible starting filter:&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;unicodedata&lt;/span&gt;

&lt;span class="c1"&gt;# Categories worth stripping from machine-ingested instruction text:
#   Cf = format (zero-width joiners/spaces, bidi controls, tag chars)
#   Cc = control (except the whitespace you actually want)
#   Co = private use
&lt;/span&gt;&lt;span class="n"&gt;STRIP_CATEGORIES&lt;/span&gt; &lt;span class="o"&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;Cf&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;Cc&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;Co&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;KEEP_CONTROLS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&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="se"&gt;\t&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="se"&gt;\r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;is_variation_selector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ch&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# Both blocks: basic (FE00-FE0F) and the supplement (E0100-E01EF).
&lt;/span&gt;    &lt;span class="c1"&gt;# These are category Mn, so the STRIP_CATEGORIES set does NOT catch
&lt;/span&gt;    &lt;span class="c1"&gt;# them — you have to name them explicitly or they slip through.
&lt;/span&gt;    &lt;span class="n"&gt;cp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;ord&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ch&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mh"&gt;0xFE00&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;cp&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mh"&gt;0xFE0F&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="mh"&gt;0xE0100&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;cp&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mh"&gt;0xE01EF&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;clean_for_prompt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# NFKC is COMPATIBILITY normalization: it folds compatibility variants
&lt;/span&gt;    &lt;span class="c1"&gt;# (e.g. fullwidth 'Ａ' -&amp;gt; 'A', ligatures, superscripts) AND composes.
&lt;/span&gt;    &lt;span class="c1"&gt;# That's deliberate here — it collapses lookalike-encodings attackers
&lt;/span&gt;    &lt;span class="c1"&gt;# lean on. It also mutates legitimate text, which is why this runs
&lt;/span&gt;    &lt;span class="c1"&gt;# ONLY at the untrusted-&amp;gt;prompt boundary. Use NFC if you need to
&lt;/span&gt;    &lt;span class="c1"&gt;# preserve compatibility distinctions.
&lt;/span&gt;    &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;unicodedata&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;normalize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;NFKC&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;out&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;ch&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;ch&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;KEEP_CONTROLS&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ch&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;continue&lt;/span&gt;
        &lt;span class="n"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;unicodedata&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;category&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ch&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;cat&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;STRIP_CATEGORIES&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="nf"&gt;is_variation_selector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ch&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="k"&gt;continue&lt;/span&gt;
        &lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ch&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The variation-selector line matters more than it looks. Both blocks are category &lt;code&gt;Mn&lt;/code&gt; (nonspacing mark), not &lt;code&gt;Cf&lt;/code&gt; — so &lt;code&gt;STRIP_CATEGORIES&lt;/code&gt; sails right past them, and a range check that only covers &lt;code&gt;FE00–FE0F&lt;/code&gt; leaves the 240-codepoint supplement (&lt;code&gt;E0100–E01EF&lt;/code&gt;) wide open. That supplement is exactly the byte channel the article opened with. Miss it and you've deployed a filter with a hole shaped like the attack you were defending against.&lt;/p&gt;

&lt;p&gt;Then the part people skip: &lt;strong&gt;log the delta, don't just drop it.&lt;/strong&gt; If a "trusted" document arrives with tag characters in it, that's a signal worth an alert, not a silent strip. One subtlety: since &lt;code&gt;clean_for_prompt&lt;/code&gt; runs NFKC &lt;em&gt;and&lt;/em&gt; strips categories, a raw length comparison can't tell you &lt;em&gt;why&lt;/em&gt; the length changed — NFKC alone can expand a ligature or fold a fullwidth glyph. If you want to claim invisibles were removed, count what the strip pass actually dropped:&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;count_stripped&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# Count only codepoints removed by the category/variation-selector
&lt;/span&gt;    &lt;span class="c1"&gt;# filter, ignoring NFKC's own length changes.
&lt;/span&gt;    &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;unicodedata&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;normalize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;NFKC&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;dropped&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;ch&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;ch&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;KEEP_CONTROLS&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;continue&lt;/span&gt;
        &lt;span class="n"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;unicodedata&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;category&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ch&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;cat&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;STRIP_CATEGORIES&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="nf"&gt;is_variation_selector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ch&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;dropped&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;dropped&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;clean_and_flag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;stripped&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;count_stripped&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;stripped&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;warning&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;stripped %d invisible codepoints from %s&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;stripped&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;source&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;clean_for_prompt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the log line asserts a cause it actually measured. If you don't care about the breakdown, at least say what you know — &lt;code&gt;"normalization changed length by %d codepoints"&lt;/code&gt; — instead of blaming invisibles for a ligature expansion you did yourself.&lt;/p&gt;

&lt;p&gt;Three rules that fall out of this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Normalize at trust boundaries, not everywhere.&lt;/strong&gt; Do it where untrusted text becomes prompt text. Don't NFKC-mangle a user's actual message history if they're allowed to type in Arabic or use combining accents — you'll corrupt legitimate content and flatten distinctions they care about. The distinction is &lt;em&gt;channel&lt;/em&gt;, not &lt;em&gt;string&lt;/em&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Never write a security filter against visible text.&lt;/strong&gt; If your blocklist, PII scrubber, or moderation check runs on the raw input, it's blind to smuggled variants — see &lt;code&gt;is_safe&lt;/code&gt; above. Run detection on the &lt;em&gt;normalized&lt;/em&gt; form, then decide what to forward.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Assume your own system prompt could be a carrier.&lt;/strong&gt; If any part of your prompt is assembled from templates pulled from a CMS, a wiki, or a Git repo multiple people edit — audit those bytes once. &lt;code&gt;grep -P '[\x{200B}-\x{200F}\x{FE00}-\x{FE0F}\x{E0100}-\x{E01EF}\x{E0000}-\x{E007F}]'&lt;/code&gt; across the repo takes seconds and occasionally finds something a copy-paste dragged in.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;And the answer to "should invisible formatting be allowed in prompt text?" is channel-specific, not universal. For machine-ingested instruction channels — system prompts, tool schemas, retrieved documents you treat as trusted — usually no. For user-visible multilingual content, often yes, because that's where these codepoints do legitimate work. Decide per channel; don't write one global rule and congratulate yourself.&lt;/p&gt;

&lt;h2&gt;
  
  
  The honest caveat
&lt;/h2&gt;

&lt;p&gt;Two things I won't overstate. First, I can't tell you Anthropic's motive, and I'd distrust anyone in that thread who claims certainty — "invisible characters appeared" is evidence of a mechanism, not of intent. It could be a tokenizer artifact, an accidental leak from an internal formatting layer, or deliberate tagging. All three produce the same bytes.&lt;/p&gt;

&lt;p&gt;Second, stripping isn't free. Bidirectional scripts need the bidi controls. Some emoji sequences need ZWJ to render a family instead of four separate people — nuke &lt;code&gt;\u200d&lt;/code&gt; globally and you break 👨‍👩‍👧. Some legitimate emoji presentation even leans on &lt;code&gt;U+FE0F&lt;/code&gt; (the emoji variation selector), which the filter above strips — fine for an instruction channel, wrong for user-facing display text. That's exactly why "strip everything, everywhere" is the wrong lesson and "normalize deliberately at the boundary you designated untrusted" is the right one.&lt;/p&gt;

&lt;p&gt;The takeaway isn't &lt;em&gt;someone is watching you.&lt;/em&gt; It's older and duller: &lt;strong&gt;a string is a byte sequence wearing a costume, and any pipeline that trusts the costume has a hole where the bytes are.&lt;/strong&gt; The watermarking story will fade in a week. The trust-boundary bug it's pointing at has been in your codebase the whole time.&lt;/p&gt;

&lt;p&gt;Go run &lt;code&gt;len()&lt;/code&gt; against your eyes on one prompt template today. See which one wins.&lt;/p&gt;

</description>
      <category>security</category>
      <category>ai</category>
      <category>programming</category>
    </item>
  </channel>
</rss>
