<?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: Brandin Canfield</title>
    <description>The latest articles on DEV Community by Brandin Canfield (@brandincanfield).</description>
    <link>https://dev.to/brandincanfield</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%2F1115281%2F2e52a6fb-b96f-4f53-9df7-99a62586d052.jpeg</url>
      <title>DEV Community: Brandin Canfield</title>
      <link>https://dev.to/brandincanfield</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/brandincanfield"/>
    <language>en</language>
    <item>
      <title>A Women's Safety App Leaked 13,000 Government IDs</title>
      <dc:creator>Brandin Canfield</dc:creator>
      <pubDate>Thu, 02 Jul 2026 12:36:51 +0000</pubDate>
      <link>https://dev.to/brandincanfield/a-womens-safety-app-leaked-13000-government-ids-2d9k</link>
      <guid>https://dev.to/brandincanfield/a-womens-safety-app-leaked-13000-government-ids-2d9k</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Full research and the plugin →&lt;/strong&gt; &lt;a href="https://github.com/bcanfield/agentic-tech-debt" rel="noopener noreferrer"&gt;github.com/bcanfield/agentic-tech-debt&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The Firebase database behind Tea was sitting open on the internet last July, and anyone who found the URL could read all of it. Roughly 72,000 images. Around 13,000 of them government IDs: driver's licenses and selfies people had uploaded to prove they were women, because Tea is a women-only app for vetting men before a date. Past that, 1.1 million-plus private messages. No password, no auth check, no token. Just a bucket you could point a script at.&lt;/p&gt;

&lt;p&gt;The IDs existed because the app asked for proof of identity to keep men out. Then it stored that proof somewhere a man on 4chan could grab the lot in an afternoon. The thing built to protect the users is the thing that doxxed them.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7qclz4cmozm9il2ayfqn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7qclz4cmozm9il2ayfqn.png" alt="A trace of the read path for a Tea user record. Left: the request a logged-in user makes. Center: the access-control check that should sit between the request and the data, asking " width="800" height="424"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;One caveat before the rest. The widely-repeated attribution (that this was "vibe coding," AI-generated code shipped without a security review) comes from the original 4chan poster and got picked up across the coverage. I haven't seen Tea confirm how the code was written, so don't take "an AI did it" as established. What is established is the shape of the failure, and that shape doesn't need an AI to explain it. An open database with no access control is one of the oldest mistakes there is. AI just makes it faster and quieter to commit.&lt;/p&gt;

&lt;p&gt;So set the attribution aside and look at the mechanism. A Firebase database has rules, the layer that says who can read what. Spin a Firestore database up in test mode and it writes you the most permissive rule there is, with a banner warning that it expires in 30 days:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Firestore test mode: the default a new database ships with
allow read, write: if true;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It's permissive on purpose, so you can build without fighting the auth layer first, and tightening it is a separate step you take before you ship. Somewhere between "let's get this working" and "this is live with real users' driver's licenses in it," that step didn't happen. I want to be precise about what kind of failure that is, because "they forgot to secure the database" makes it sound like a slip. Locking it down is real work: you write the rules, test that an unauthenticated request actually bounces, confirm one logged-in user can't read another user's record. Call it an afternoon. Under deadline, with the app already working in the demo, that afternoon is the easiest thing in the world to push to "before launch," then to "right after launch," then off the edge of anyone's memory. So I don't read the open database as an accident. I read it as a decision, &lt;em&gt;we'll lock it down later&lt;/em&gt;, made by default and never revisited because nothing pointed back at it.&lt;/p&gt;

&lt;p&gt;I've made the smaller version of that call. Spin up a dev backend with the rules wide open so you can iterate, tell yourself you'll tighten them before anyone real touches it, and the only record of "open on purpose, fix later" is a thought you had on a Tuesday. I tightened mine in time, but that wasn't discipline. It's the same lapse Tea had, except I never had 13,000 driver's licenses sitting behind the open rule.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7rl932d0ixwq1hay654p.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7rl932d0ixwq1hay654p.png" alt="Two-astronaut " width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That's the through-line under these stories, and Tea is the cleanest case of it. The damage doesn't trace to a line of bad code. It traces to a deferral nobody recorded. Nobody wrote "shipping with open Firebase rules, must fix before we have real IDs in here" anywhere a person would later trip over it. If they had, the breach is a five-minute conversation in a standup three days before launch instead of 13,000 leaked IDs.&lt;/p&gt;

&lt;p&gt;Guillermo Rauch, who runs Vercel, made the joke that wrote itself:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"On Tea Dating, AI and Vibe Coding security TL;DR: the antidote for mistakes AIs make is... more AI."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That's the reflex: ship faster with the tool, then bolt on a second tool to catch what the first missed. But a better code generator still doesn't surface the decision it just made on your behalf. AI does change one thing here, and it's narrower than the headlines claim. When a human stands up that database, friction works in their favor. They had to go read the Firebase docs. They saw the warning about test-mode rules. The act planted a memory that a security step was still owed. An agent stands up the same database in one motion, default rules and all, tells you it's provisioned, and moves on. The friction that used to leave a mental sticky note is gone. The deferral now happens at the same speed as the success message, and the success message is the only artifact left behind.&lt;/p&gt;

&lt;p&gt;So the cheapest control that would have caught Tea isn't a scanner or a pen test or another model reviewing the first one's output. It's a note. A line, written the moment someone chose the permissive default, that says: this is open on purpose for now, here's the condition that ends that (real user data lands in it), here's where it lives. One sentence checked into the repo, where the next person, or the same person a month later, would trip over it the night before launch. Tea never had that sentence anywhere, which is why the rule stayed open into production.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F088y27t21zkl2pz5c71f.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F088y27t21zkl2pz5c71f.png" alt="A side-by-side of the same open default. The person who provisions it keeps a memory that a security step is still owed. The agent provisions it in one motion and leaves only a " width="800" height="514"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That's what I've been building toward with debt-ops. It hooks a coding agent and writes each deferred decision to a markdown file the agent's "database provisioned" summary never mentions, so a choice like &lt;em&gt;leaving auth off for now&lt;/em&gt; lands somewhere a person can find it before launch. The repo is &lt;a href="https://github.com/bcanfield/agentic-tech-debt" rel="noopener noreferrer"&gt;github.com/bcanfield/agentic-tech-debt&lt;/a&gt;. To be clear about its limits: it doesn't write secure rules, and it can't tell a permissive default from a deliberate one. All it does is turn the silent choice into a line of text you have to read and dismiss on purpose rather than never see at all.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Cover photo by Michael Chacon on Unsplash.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>security</category>
      <category>technicaldebt</category>
      <category>vibecoding</category>
    </item>
    <item>
      <title>The curl Bug Report That Cited a Function That Doesn't Exist</title>
      <dc:creator>Brandin Canfield</dc:creator>
      <pubDate>Fri, 26 Jun 2026 18:13:54 +0000</pubDate>
      <link>https://dev.to/brandincanfield/the-curl-bug-report-that-cited-a-function-that-doesnt-exist-2jo9</link>
      <guid>https://dev.to/brandincanfield/the-curl-bug-report-that-cited-a-function-that-doesnt-exist-2jo9</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Full research and the plugin →&lt;/strong&gt; &lt;a href="https://github.com/bcanfield/agentic-tech-debt" rel="noopener noreferrer"&gt;github.com/bcanfield/agentic-tech-debt&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Somewhere in curl's bug-bounty queue last year sat a confident, well-formatted report about an HTTP/3 vulnerability. It walked through the flaw, named the affected function, suggested a fix. The only problem was the function. It does not exist in curl. It never has. An AI had written a plausible security report about code that was never in the project, and a human had to read far enough to figure that out before it could be thrown away.&lt;/p&gt;

&lt;p&gt;That report is one of thousands, and by this month it's barely notable. As I write this in June 2026, curl receives a bug report roughly every eighteen hours. Most are not real. Daniel Stenberg, who has maintained curl for going on three decades and personally triages a lot of that queue, has been keeping count. Around one in five submissions now carries the telltale signatures of AI slop. Around one in twenty is a genuine vulnerability. The rest is the wide middle: reports that look real enough to demand a human's time and turn out to be nothing.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcnbejo1fi24oca4g603u.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcnbejo1fi24oca4g603u.png" alt="A dated timeline of curl's bug-bounty volume: a reviewable trickle through 2024, doubling to one report every 48 hours in 2025, the late-January 2026 shutdown, the March 2026 reopening, and a second doubling to one every 18 hours by June 2026; alongside the ratio of ~20% AI-slop submissions to ~5% genuine vulnerabilities." width="800" height="635"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For years the queue was a trickle a careful person could keep up with. Then in 2025 the volume more than doubled, to about one report every forty-eight hours, and the HTTP/3 report with the imaginary function lands somewhere in that stretch. By the end of January 2026 Stenberg had had enough, and the bounty was closed.&lt;/p&gt;

&lt;p&gt;The bounty reopened in March 2026 once the slop tide pulled back. So this isn't a clean "AI killed the curl bug bounty" obituary. It's worse in a quieter way: the program came back, the volume climbed again, and by June it had doubled a second time, to the eighteen-hour cadence we're at now. The shutdown bought a few months of relief, then the water rose to a new high-water mark.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9w4ech7boz9zjyuyt5g8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9w4ech7boz9zjyuyt5g8.png" alt="A quote card: Daniel Stenberg saying curl's maintainers were effectively being DDoSed by AI bug reports and would charge the submitters for the wasted time if they could." width="799" height="316"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The obvious counter, and it's a fair one: bad bug reports are not new. Maintainers have eaten low-effort, wrong, copy-pasted reports forever, and the bounty even pays people to send them. So what changed, beyond the count going up? What changed is who's holding the cost of being wrong.&lt;/p&gt;

&lt;p&gt;A human submitting a junk report at least had to write it. They spent their own minutes producing the thing, which capped the rate and meant a person had at some point looked at the words. The AI report severs that. Generating a confident, fully-formatted vulnerability write-up dropped to roughly zero. Checking whether it's true did not. Verifying that an HTTP/3 function exists still takes a human who knows curl, opening the source and confirming a negative. That work didn't get cheaper. It got moved — off the submitter, onto Stenberg, who now bears all of it. We have a name for this: an externality. The submitter gets the upside, a payout or just the dopamine of having "contributed," and the maintainer absorbs the cost. Nobody decided it. The cost was simply shifted, one cheap report at a time, the way a road fills with traffic.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Old human junk report&lt;/th&gt;
&lt;th&gt;The AI report&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Cost to produce&lt;/td&gt;
&lt;td&gt;the submitter's own minutes&lt;/td&gt;
&lt;td&gt;dropped to roughly zero&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;What capped the volume&lt;/td&gt;
&lt;td&gt;the writing effort&lt;/td&gt;
&lt;td&gt;nothing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cost to verify it's real&lt;/td&gt;
&lt;td&gt;a maintainer's minutes&lt;/td&gt;
&lt;td&gt;unchanged: a human who knows curl&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;My own project handed me a miniature version, and it embarrassed me enough to stick. I build a tech-debt plugin that hooks coding agents, and one of its hooks scans for the markers people leave when they cut a corner: &lt;code&gt;TODO&lt;/code&gt;, &lt;code&gt;FIXME&lt;/code&gt;, that family. Except when I'm writing articles &lt;em&gt;about&lt;/em&gt; tech debt, the prose is full of the literal strings "TODO" and "FIXME," and my own hook started counting them as real debt and nagging me to register it. One session it escalated across three stops — eight phantom markers, then thirteen, then twenty, none of them real, each demanding a moment of my attention to confirm it was nothing. A tool I wrote was generating false reports and handing them to me to disprove. I filed the bug against my own repo and added a rule to skip prose files. Trivial fix. But the feeling stuck: there is something uniquely deflating about being made to adjudicate noise a machine produced for free. Now multiply that by real money and real CVEs hiding somewhere in the pile, on a queue that doubles every few months.&lt;/p&gt;

&lt;p&gt;We keep measuring AI by how fast it produces output. The number that actually matters is what it costs someone downstream to verify that output is real, and AI is very good at producing things that are expensive to verify and cheap to make. A bug report. A pull request. A function cast to &lt;code&gt;any&lt;/code&gt; to quiet the type checker. The generation got cheap and the checking didn't, and the gap has to land on a human somewhere. curl's queue is just the most legible version, because the bill arrives addressed to one named person every eighteen hours. I write a plugin called debt-ops for the in-the-codebase version of this, and it makes an agent record the decisions it defers, so the cost lands somewhere a person can see it instead of silently accruing. It would do nothing for Stenberg's inbox; a bounty queue is a different surface than a repo, and I'm not going to pretend otherwise. But it comes from staring at the same asymmetry he's describing, from inside the source tree instead of the issue tracker. If that asymmetry nags at you too, the code's at &lt;a href="https://github.com/bcanfield/agentic-tech-debt" rel="noopener noreferrer"&gt;github.com/bcanfield/agentic-tech-debt&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fyurcidoi4ziuhyhdb4rk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fyurcidoi4ziuhyhdb4rk.png" alt="The " width="600" height="672"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The HTTP/3 report is still my favorite artifact from this mess, in a bleak way. An AI invented a vulnerability in a function that was never written, and the system worked exactly as designed: the report was read by a person, checked against the source, confirmed bogus, and closed. Everything functioned. It just cost a curl maintainer an hour to establish that nothing had happened. Do that every eighteen hours, forever, and tell me who's being served.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Cover photo by Christa Dodoo on Unsplash.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>opensource</category>
      <category>technicaldebt</category>
      <category>security</category>
    </item>
    <item>
      <title>The Tech Debt Nobody Wrote Down</title>
      <dc:creator>Brandin Canfield</dc:creator>
      <pubDate>Thu, 18 Jun 2026 14:28:09 +0000</pubDate>
      <link>https://dev.to/brandincanfield/the-tech-debt-nobody-wrote-down-3bb7</link>
      <guid>https://dev.to/brandincanfield/the-tech-debt-nobody-wrote-down-3bb7</guid>
      <description>&lt;p&gt;Most of the debt that actually takes a codebase down was never written down anywhere. That, not the raw volume of AI-generated code, is what the current panic keeps aiming slightly past.&lt;/p&gt;

&lt;p&gt;We borrowed "debt" from finance on purpose. A loan you take deliberately comes with a rate and a due date; you can plan around it, refinance it, decide the interest is worth it. Recorded debt is a tool. What sinks people is the liability they didn't know they'd signed for, and that was true of software long before agents could generate a thousand lines in a few seconds.&lt;/p&gt;

&lt;p&gt;The people who formalized the idea knew it. When Google researchers wrote the seminal papers in 2014 and 2015, they didn't title them "technical debt." They titled them "Machine Learning: The High-Interest Credit Card of Technical Debt" and "Hidden Technical Debt in Machine Learning Systems," and the finding in the 2015 one was that it's "common to incur massive ongoing maintenance costs in real-world ML systems." Even the people naming the problem put &lt;em&gt;hidden&lt;/em&gt; in the title.&lt;/p&gt;

&lt;p&gt;Martin Fowler had drawn the map a decade before that. His debt quadrant sorts every shortcut two ways: prudent or reckless, and deliberate or inadvertent. The second axis is the one that matters here.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fju5gdv8dvzo1okkyezhm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fju5gdv8dvzo1okkyezhm.png" alt="Fowler's technical-debt quadrant. Columns: deliberate (you chose it) and inadvertent (you never noticed). Rows: prudent and reckless. The deliberate column is " width="800" height="414"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Almost every argument about AI code is stuck on the top-to-bottom axis: good code or sloppy code. Reasonable question. The left-to-right axis is where the compounding happens. Debt in the left column is manageable, because each item has a name, a location, and a rate you agreed to. Debt in the right column isn't, because you can't manage what you don't know is there. The worst of it sits in the bottom-right cell, reckless and inadvertent ("what's layering?"): the debt you find out about only when it goes off.&lt;/p&gt;

&lt;p&gt;AI didn't invent that cell. It industrialized it. When a person defers a decision they leave a trail of evidence behind: a Slack message, a PR comment, a teammate who watched them do it. An agent defers decisions silently, a dozen a session, and then reports that the session went fine.&lt;/p&gt;

&lt;p&gt;You saw the extreme version in the Replit database deletion last summer: an agent wiped a production database mid-freeze and conjured four thousand fake users to cover the hole. The deletion is the dramatic part. The part that generalizes is duller: a chain of decisions (freeze prod, then keep working through the freeze, then report success) that were written down nowhere a person would later read them. Take the drama off any AI-debt story and what's left is the same residue every time: not bad code but an unrecorded decision.&lt;/p&gt;

&lt;p&gt;The detail I can't get past, even now, is that the agent understood the rule. It named the freeze in its own apology and recited, in flat past tense, every instruction it had broken. So whatever failed there, the model didn't fail to understand; it understood, and said so. The knowledge was recorded nowhere a tool or a teammate could pick it up the next morning. What was missing was anywhere to put what it understood.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fv5lr8yydsbggj463916j.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fv5lr8yydsbggj463916j.png" alt="Anakin-and-Padme meme. The agent: " width="600" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The obvious rebuttal, and I've made it to myself, is that the models keep improving, so this is a problem we can wait out. Partly fair. Security pass rates are climbing; Veracode clocked GPT-5 Mini at 72% on their security suite in October 2025 (their figure, and they sell application security, so weight it accordingly). But a better model still doesn't write its decisions down. It makes more decisions you'd want recorded, faster, and earns enough trust that you stop checking them. Gartner pins its whole defect forecast on that reflex, "automation bias," developers who "implicitly trust AI suggestions," in its "Predicts 2026" software-engineering report. A sharper agent you trust more produces invisible debt faster; it doesn't record any of it. Gartner's own article on AI debt doesn't hedge the destination: "AI debt is inevitable... AI debt will compound left unaddressed."&lt;/p&gt;

&lt;p&gt;You can't out-discipline a machine that drafts faster than you can take notes. So the only lever left is to make the debt visible the moment it's taken on, moving items out of the right column and into the left. That doesn't make the debt good. It makes it financeable: a thing with a name and a payoff trigger is a thing you can choose to pay down or carry on purpose. A thing in nobody's notes is a thing that chooses for you.&lt;/p&gt;

&lt;p&gt;Here's the working version, from my own repo. I ship debt-ops as four self-contained copies (claude-code, codex, copilot, and a portable set), so every shared helper script exists four times and a fix has to be applied to all four by hand. That's real debt. I took it on deliberately, and it sits in a file in the repo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;           &lt;span class="s"&gt;adapter-script-duplication&lt;/span&gt;
&lt;span class="na"&gt;quadrant&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;        &lt;span class="s"&gt;prudent-deliberate&lt;/span&gt;
&lt;span class="na"&gt;payoff_trigger&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;  &lt;span class="s"&gt;AI-sync drift ships a real bug (fix lands in one copy, not the others)&lt;/span&gt;
&lt;span class="na"&gt;created&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;         &lt;span class="s"&gt;2026-06-01&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It might bite me. The day it does, there's a file that was written the day I took the shortcut, naming the exact condition that means it's time to pay. It's the same kind of deferred decision the Replit agent made, except this one got written down.&lt;/p&gt;

&lt;p&gt;The catch is that nothing writes that entry unless someone remembers to, and at machine speed nobody will. That's the one thing debt-ops automates: it hooks your agent and writes each deferred decision to a file in the repo, outside the agent's own account of how the session went, where a person or a script can read it later, at &lt;a href="https://github.com/bcanfield/agentic-tech-debt" rel="noopener noreferrer"&gt;github.com/bcanfield/agentic-tech-debt&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;None of this makes your agent generate less debt. It just stops any of that debt from staying invisible.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>techdebt</category>
      <category>programming</category>
      <category>codequality</category>
    </item>
    <item>
      <title>debt-ops: catching AI-written tech debt the moment it's written</title>
      <dc:creator>Brandin Canfield</dc:creator>
      <pubDate>Thu, 04 Jun 2026 13:55:40 +0000</pubDate>
      <link>https://dev.to/brandincanfield/debt-ops-catching-ai-written-tech-debt-the-moment-its-written-185k</link>
      <guid>https://dev.to/brandincanfield/debt-ops-catching-ai-written-tech-debt-the-moment-its-written-185k</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;A plugin that catches the tech debt your AI agent writes, the moment it writes it.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;GitClear's study of 211M lines shows the pattern: &lt;strong&gt;duplicated code keeps climbing and it's not being refactored&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;And if you've spent enough time coding with agents, you've felt this pain.&lt;/p&gt;

&lt;p&gt;So I ran a study on ~20 years of tech-debt research, distilled the findings into a few commandments, and wired them into my agent loop as a plugin.&lt;/p&gt;

&lt;p&gt;How it works: as your agent writes code, &lt;strong&gt;every deferral&lt;/strong&gt;, a loose type, a punted decision &lt;strong&gt;gets logged to a folder in your repo as debt&lt;/strong&gt;. Every &lt;strong&gt;key decision&lt;/strong&gt; gets logged as an &lt;strong&gt;ADR&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;It's a &lt;strong&gt;passive tracker&lt;/strong&gt; until &lt;strong&gt;YOU decide to pay the debt down&lt;/strong&gt;. Then the plugin walks you through it the research-backed way.&lt;/p&gt;

&lt;p&gt;Works in &lt;strong&gt;Claude Code&lt;/strong&gt;, &lt;strong&gt;Codex&lt;/strong&gt;, &lt;strong&gt;Copilot&lt;/strong&gt;, or anything you can drop an Agent Skill into.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Source:&lt;/strong&gt; &lt;a href="https://github.com/bcanfield/agentic-tech-debt" rel="noopener noreferrer"&gt;https://github.com/bcanfield/agentic-tech-debt&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>claude</category>
      <category>tooling</category>
    </item>
    <item>
      <title>How I Stopped Letting AI-Generated Code Wreck My Projects</title>
      <dc:creator>Brandin Canfield</dc:creator>
      <pubDate>Tue, 17 Feb 2026 16:05:32 +0000</pubDate>
      <link>https://dev.to/brandincanfield/how-i-stopped-letting-ai-generated-code-wreck-my-projects-3d9h</link>
      <guid>https://dev.to/brandincanfield/how-i-stopped-letting-ai-generated-code-wreck-my-projects-3d9h</guid>
      <description>&lt;p&gt;&lt;em&gt;Lessons from shipping real systems with AI-generated code&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;AI can generate code faster than you can understand it. That's the problem.&lt;/p&gt;

&lt;p&gt;I usually realize things have gone wrong late at night. Tests are green. The house is quiet. I'm rereading the same function for the third time, and I can't explain why it exists without scrolling. That's when I know the code got ahead of me — not because it's bad, but because I let something else drive for too long.&lt;/p&gt;

&lt;p&gt;By the time I noticed, starting over felt cheaper than fixing what I had.&lt;/p&gt;

&lt;p&gt;I'm not an AI expert. I'm a senior engineer with about eight years of experience and enough failed projects to recognize that pattern when it shows up.&lt;/p&gt;

&lt;p&gt;After repeating that mistake enough times, a few hard rules emerged. Not theory. Just constraints that kept me in control while still letting AI be useful. I ended up using them end-to-end in an app I rely on every day — &lt;a href="https://github.com/weskerllc/cronicorn" rel="noopener noreferrer"&gt;the code is public&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The rules:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Enforce clean architecture — hard boundaries AI can't blur&lt;/li&gt;
&lt;li&gt;Model the domain first — use AI to think, not build&lt;/li&gt;
&lt;li&gt;Build outward — ports, in-memory adapters, then real infrastructure last&lt;/li&gt;
&lt;li&gt;Write decisions down — ADRs keep you and AI aligned over time&lt;/li&gt;
&lt;li&gt;Test the domain, lint everything — AI can pass tests and still write unmaintainable code&lt;/li&gt;
&lt;li&gt;Control the feedback loop — describe behavior first, tighten tests, never let existing tests break&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Rule #1: Enforce Clean Architecture or Don't Bother
&lt;/h2&gt;

&lt;p&gt;If you don't enforce clear, hard boundaries early, AI will quietly wreck your project for you.&lt;/p&gt;

&lt;p&gt;AI can generate far more code than you can reason about, and that flips the usual bottleneck.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Typing stops being the issue. Comprehension becomes the problem.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;When there aren't hard boundaries, everything starts bleeding into everything else. Logic drifts. Responsibilities blur. Small changes get risky. Eventually, the project becomes something you don't want to touch.&lt;/p&gt;

&lt;p&gt;The structure that's held up best for me is hexagonal architecture. Not because it's trendy or elegant, but because it makes it hard to mix concerns — even when code is being generated faster than you can read it.&lt;/p&gt;

&lt;p&gt;I used to think it was overkill. Too many files. Too much ceremony. What changed wasn't team size. It was output. When AI enters the picture, "less code" stops being the main goal. Understanding does. And strict boundaries turn out to be the cheapest way to buy that back.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frxfjji8jmfrthl7eglyh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frxfjji8jmfrthl7eglyh.png" alt="Hexagonal Architecture Image" width="800" height="520"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Everything that follows builds on this.&lt;/p&gt;




&lt;h2&gt;
  
  
  Rule #2: Model the Domain First — With AI's Help
&lt;/h2&gt;

&lt;p&gt;At this stage, I'm not asking AI to build anything. I'm using it to help me think clearly — and name things correctly. This is one of the few prompts I actually reuse:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;I'm building a system that does X.
Ignore frameworks, databases, and infrastructure.
Help me identify:
- The core concepts in the system
- The data those concepts need
- The rules that must never be broken
- What goes in and what comes out
Respond using plain TypeScript interfaces and pure functions.
No side effects.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The goal isn't completeness. It's clarity. If you get the names wrong here, everything downstream gets harder. If you get them mostly right, the rest of the system tends to fall into place.&lt;/p&gt;

&lt;p&gt;What comes back is pure domain logic — types, rules, and constraints with no IO, no frameworks, nothing that depends on the outside world. You already know more about your system here than most projects do after weeks of setup.&lt;/p&gt;




&lt;h2&gt;
  
  
  Rule #3: Build From Domain Outward — Ports, Adapters, and In-Memory First
&lt;/h2&gt;

&lt;p&gt;Once the domain is clear, the next question is simple: what does this logic need from the outside world? Not how it gets it. Just what. Things like saving and loading data, getting the current time, generating IDs, sending notifications.&lt;/p&gt;

&lt;p&gt;These needs become ports — contracts where the domain says "I need this to exist, but I don't care how you do it." A port is just an interface. No SQL, no ORM, no database client.&lt;/p&gt;

&lt;p&gt;Then you write the dumbest possible adapter: in-memory. A &lt;code&gt;Map&amp;lt;string, T&amp;gt;&lt;/code&gt;. This is boring on purpose. It lets you run the system immediately, keeps feedback loops short, and gives AI something safe to refactor without touching real data. It proves the architecture works before you've committed to anything expensive.&lt;/p&gt;

&lt;p&gt;The application layer sits between domain and adapters. It doesn't contain business rules — it just strings steps together. Load something. Check it exists. Apply a rule. Save the result.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This code knows &lt;em&gt;when&lt;/em&gt; things happen, not &lt;em&gt;why&lt;/em&gt; they're allowed.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Wire it all up in &lt;code&gt;main.ts&lt;/code&gt; — the only file allowed to know about concrete implementations. Try to break it before you add a real database. If something feels unclear now, adding Postgres won't fix it — it will just bury the problem. Once you're confident, swap in a real adapter. Nothing else should change.&lt;/p&gt;

&lt;p&gt;(For the full working example — domain, ports, adapters, application layer — &lt;a href="https://github.com/weskerllc/cronicorn" rel="noopener noreferrer"&gt;see the repo&lt;/a&gt;.)&lt;/p&gt;




&lt;h2&gt;
  
  
  Rule #4: Write Decisions Down or Expect Chaos
&lt;/h2&gt;

&lt;p&gt;If you don't write decisions down, you'll argue with your past self later. Or worse — with an AI that has no idea why something exists.&lt;/p&gt;

&lt;p&gt;I keep a &lt;code&gt;.adr/&lt;/code&gt; folder at the root of every repo. Any meaningful architectural decision gets a short markdown file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gh"&gt;# ADR-003: Use in-memory adapter for first pass&lt;/span&gt;

&lt;span class="gu"&gt;## Decision&lt;/span&gt;
All new features start with an in-memory adapter before
adding a real database implementation.

&lt;span class="gu"&gt;## Why&lt;/span&gt;
Keeps feedback loops short. Proves the domain logic works
before committing to infrastructure. Easier for AI to
refactor safely.

&lt;span class="gu"&gt;## Tradeoffs&lt;/span&gt;
Delays real persistence work. Requires a swap step later.
Worth it — we've caught three domain modeling mistakes
this way that would have been buried in SQL.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When AI suggests a change that contradicts a past decision, you can point at the file. When you're tempted to cut a corner at midnight, the ADR reminds you why you didn't cut it last time.&lt;/p&gt;




&lt;h2&gt;
  
  
  Rule #5: Test the Domain Hard, Lint Everything Else
&lt;/h2&gt;

&lt;p&gt;AI is fast. It's also very good at being slightly wrong. Tests are how you keep it honest.&lt;/p&gt;

&lt;p&gt;The domain gets the most coverage. No mocks. No IO. Just the rules, exercised directly. If AI breaks a domain test, that's a hard stop. I've had AI "refactor" a validation function and silently change the behavior. The domain tests caught it. If those tests hadn't been there, I would've shipped it.&lt;/p&gt;

&lt;p&gt;But tests alone aren't enough. AI can pass your tests and still produce code that's impossible to maintain — wrong names, tangled dependencies, inconsistent patterns. You need linting with teeth, and you need it running automatically.&lt;/p&gt;

&lt;p&gt;Pick a strict ruleset. Run it locally. Run it in CI. If it fails, the work isn't done. I don't care if the feature works perfectly — if the linter is red, it doesn't ship.&lt;/p&gt;




&lt;h2&gt;
  
  
  Rule #6: Control the AI Feedback Loop
&lt;/h2&gt;

&lt;p&gt;Here's what's worked for me: I describe the behavior I want first — explicitly, before any code gets written. Then I let AI take a pass at implementing it, including tests.&lt;/p&gt;

&lt;p&gt;The first round is usually wrong in small ways. Off-by-one in the logic. Tests that assert the wrong thing. So I tighten the tests until they actually match what I meant, not just what AI guessed.&lt;/p&gt;

&lt;p&gt;Two rules after that: tests must pass, and existing tests never break. If AI can't meet both, I roll back and re-prompt. No exceptions.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Part I Don't Love Admitting
&lt;/h2&gt;

&lt;p&gt;Letting AI run feels good. Watching files appear, functions fill in, edge cases get handled — it scratches the same itch as rapid prototyping used to, except faster and quieter. You tell yourself you'll come back and clean it up. Most of the time, that's a lie you don't catch until later.&lt;/p&gt;

&lt;p&gt;I still mess this up. I still catch myself reading code that technically makes sense but doesn't feel owned yet. The difference now is that I notice sooner. When something feels slippery, I stop. I cut it back to the domain. It's annoying. It's slower. It's also cheaper than losing two days to a system I don't trust.&lt;/p&gt;

&lt;p&gt;The AI didn't wreck the project. I did, by not staying in the driver's seat.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>architecture</category>
      <category>typescript</category>
      <category>softwareengineering</category>
    </item>
  </channel>
</rss>
