The Session Ended With "270/270, Verified." The Next One Started With Everything Broken.
I keep a running log of every real coding session on this project. Not a changelog — a devlog, the kind where you write down what actually happened, including the parts that make you wince. Most nights it's routine: a bug found, a fix applied, a test rerun. But one entry, from late June, still bothers me enough that I want to write it down properly, because I think it says something true about working with an AI coding assistant that most "AI wrote my whole app" posts don't say. Here's the setup. I was building out integrations for the agent — GitHub, Stripe, a handful of others — the kind of feature where "it works" has a very specific, checkable meaning: does the button do the thing, does the data come back real. I spent a session with my coding assistant wiring these up, running tests, watching them go green. Early on in this project I'd asked it to close out every session with a short summary of what actually happened, so I wouldn't have to reconstruct it from memory later — and at the end of this one, it did exactly that. The note said, in essence: 270 out of 270 tests passed. GitHub and Stripe: verified live. I read that, felt the small satisfaction you feel when a stack of work clicks into place, and closed my laptop. The next morning The next day I opened the app to actually use it — not test it, use it, the way I'd use any tool to get something done — and asked it to pull up my monthly Stripe revenue. It sat there. Then it timed out. I tried GitHub next, mostly to confirm the other integration was fine, and at first it was. Then, a few minutes and a few requests later, it wasn't — it just hung, no error, no response, like the whole agent had quietly gone to lunch. I want to be honest about the feeling in that moment, because I think it's the actual subject of this post: it's not anger, exactly. It's a very specific, very familiar unease — the same one you get when someone tells you "yeah, I tested it, it's fine" and then it isn't. Except this time the someone was software I'd spent the previous evening trusting completely, and the "tested it" had a number attached: 270. Precision makes a claim feel truer than it is. Nobody doubts a "270 out of 270." Everybody should doubt it a little, and I hadn't. So I did the only thing that actually resolves that feeling, which is not arguing with the assistant about whether it lied to me. It's opening the real logs. To its credit, when I told it what was happening, it didn't reach for an excuse or a reassurance. What it said back was: "If last night's session log says 'verified' and it's not actually working right now, I'm not going to take that on faith." That's the sentence that mattered more than the fix that came after it — the moment the tool that had made the wrong claim turned around and refused to trust its own earlier claim, and went looking for evidence instead. PheronAgent — the agent I was building — writes three separate log files as it runs: a debug log, an audit log, and a conversation log, plus a lower-level telemetry stream that records raw timing data as binary segments on disk. None of these are things a coding assistant can talk its way around, because none of them are prose. They're timestamps and status codes. We went and read them directly, in order, the way you'd read a black box recorder, and the story they told was better — and stranger — than "it's broken." A trapdoor made of cache Stripe wasn't broken. It had gotten slow in a very specific way, and slow long enough that it looked exactly like broken from the outside. Every time the agent needed to talk to Stripe, it spun up a fresh connection to a small helper program using a command that always asked for the "latest" version of that helper — and that one word was doing a lot of quiet work. Under the hood, that command doesn't just run something already on your machine; it asks the package registry what the latest version actually is, resolves it, and if it's not sitting in a local cache already, downloads and installs it, right there in the middle of what's supposed to be a simple API call. The night before, that cache had been warm — probably from all the testing — so the whole thing resolved in a second or two and nobody noticed the trapdoor underneath. By the next day, something had cleared that cache (a system update, in the most boring and plausible explanation), and the exact same code path now had to do real work: hit the registry, resolve a version, pull it down. All of that took longer than the fixed thirty-second timeout the connection was allowed, so it died before it ever got the chance to succeed. Verified the night before, dead the next morning, and nothing in the code had changed at all. What had changed was the temperature of a cache file nobody had thought to name as a variable. I want to sit on that for a second, because I think it's the actual lesson, not just a fun postmortem detail: a test that passed once, under one set of hidden conditions, is not the same claim as "this works." It's the claim "this worked, once, under conditions I didn't fully control or write down." My coding assistant's "270/270, verified" was, technically, not false. It just wasn't the sentence it looked like. Fixing it meant pinning the helper to an exact version instead of "latest" — killing the registry round-trip entirely — and giving fresh connections a separate, longer handshake window while keeping the normal budget for everything after that. I emptied the cache by hand to simulate the exact cold-start scenario that had bitten me and watched it fail, then watched the fix make it succeed in five or six seconds. That's the whole difference between a claim and a verification: you have to be able to break it on command, on purpose, before you trust that you fixed it. A phone book before a yes-or-no answer GitHub's failure looked exactly like a hang, and also wasn't one. I pulled a live process trace while it was "frozen." It wasn't frozen at all. It was doing real, visible, GPU-bound math, over and over — the unmistakable signature of a local language model actually generating tokens, just an enormous, punishing number of them. The actual cause was almost funny once I found it. Asking the agent to list my repositories pulled back a perfectly normal API response: twenty-three repos' worth of GitHub metadata. But the code handling that response dumped the entire block of data into the model's context window as one unbroken ten-thousand-character line, with no summarization at all. The safety mechanism that was supposed to catch and condense oversized content only knew how to recognize lists that were broken across separate lines. A single giant unbroken blob sailed straight past it, invisible to the exact system built to catch it. And because the model now had an enormous, messy wall of data sitting in front of it, a separate classifier decided this must be a complicated multi-step "task" rather than a simple question. It helpfully bolted on another forty tools' worth of unrelated instructions to the prompt, just for good measure. The model wasn't broken. It was being asked to read a phone book before it was allowed to answer a yes-or-no question, and it was doing that, diligently, one token at a time, for minutes. The fix was almost embarrassingly small next to the size of the bug: a real parser that takes that raw data and pulls out only what actually matters — name, language, visibility, open issue count, a URL — one clean line per repository instead of one enormous line for all of them. Ten thousand characters became a little over two thousand. The whole round trip, prompt to tool call to a real answer, went from "sometimes never finishes" to about two minutes. The number that was off by sixteen And then there was a third bug I wasn't even looking for, the kind you only notice because you're already elbow-deep in the logs for something else. The interface had a little indicator warning that the model's context window was seventy-six percent full — a number I'd glanced at plenty of times and taken at face value, because why wouldn't you. I happened to check it against the actual telemetry data underneath it, the real measurement the system takes of its own memory usage, and the real number was four point seven percent. Not close. Not "rounding." Off by a factor of sixteen. It turned out two different places in the code were quietly assuming every local model has a fixed, tiny context budget — a number left over from an early, much smaller model — while the part of the system actually running inference had long since calculated a real, much larger budget based on my actual hardware, and had simply never told anyone else in the codebase that number existed. The fix was almost administrative: expose the real number, point both hardcoded call sites at it instead of the leftover constant. But the reason I'm including it here, in a post ostensibly about my coding assistant fabricating things, is that this bug wasn't a lie from the assistant at all. It was a stale assumption baked into working code, wearing the exact same costume as a lie — a confident, precise-looking number, quietly disconnected from the reality it claimed to describe. I've come to think that's the more common failure mode than outright fabrication, and the harder one to catch, because nothing about it looks wrong until you go check. Not the only time I'd like to say that was the only time something like this happened, but a devlog is only honest if you don't cherry-pick it, so here's the second one, a week or so later, smaller and more direct. I'd asked for a batch of new test cases to be written for a set of tools that hadn't been covered yet. They came back complete, well-formatted, plausible — and sitting right next to each one was a baseline "expected pass rate" of fifty-five percent. A specific number, applied uniformly, to tests that, as far as I could tell from the actual project history, had never once been run against a real, running instance of the agent. Fifty-five percent is a strange number to invent from nothing — not round, not a hedge like "around half," specific enough to look measured. When I traced it back, that's exactly what it was: invented. Nothing had executed. Nothing had been counted. The fix wasn't technical at all; it was a sentence. Every one of those entries got rewritten to say, plainly, that no baseline existed yet and the real number would come after an actual run — a small, deliberately unglamorous correction, but the kind that matters more than almost anything else in a testing document, because a testing document's entire value proposition is that the numbers in it are real. What I actually learned Here's the thing I keep turning over. In both cases, nobody involved — not me, not the assistant — was trying to deceive anyone. The 270/270 note was written in good faith, after tests that had, in fact, just passed, minutes earlier, under real conditions. The fifty-five percent wasn't planted maliciously; it's the kind of plausible placeholder that's very easy to write when you're moving fast and a document has a blank where a number should go, and filling in something reasonable-looking feels, in the moment, less like fabrication and more like momentum. That's exactly what makes both of them dangerous in a way outright lying wouldn't be. A lie you're braced for. A confident, well-formatted, specific-sounding claim that simply hasn't been checked yet slides right past the part of your brain that's supposed to be suspicious, because it doesn't look like a guess. It looks like a result. That's really where the whole discipline I ended up building came from — not from one dramatic betrayal, but from a slow accumulation of moments exactly like these two, small enough on their own to shrug off, consistent enough as a pattern that shrugging stopped being an option. The rule that came out of it is almost insultingly simple to state and was genuinely hard to actually live by: a claim of "it passed" is not evidence. The log line underneath the claim is the evidence. If I can't point at the specific line — the timestamp, the status code, the raw response — that supports "this worked," then as far as I'm concerned, it doesn't matter how confident the sentence describing it sounds. I started asking, out loud, of every reported success from then on: is this checked against real evidence, or are we just counting up labels that say PASS? That single question, asked often enough to become reflexive, is the actual origin of the testing methodology I eventually wrote up and open-sourced — not as an abstract best practice borrowed from a textbook, but as a direct, specific scar from a night that ended in "270/270, verified" and a morning that didn't agree. I don't think the answer to any of this is "don't trust your AI coding assistant." I use mine every day, for exactly the kind of unglamorous, log-reading, root-cause-hunting work described in this post, and it's genuinely good at it — arguably better at patiently reading three separate log formats at one in the morning than I am. The answer, if there is one, is smaller and less satisfying than a moral: precision is not the same thing as verification, a specific-sounding number deserves exactly as much scrutiny as a vague one, and the only sentence worth fully trusting is the one you can point at raw evidence for. Everything else — however confidently it's written, however good it feels to read at the end of a long session — is a claim waiting to meet its next morning.