I had my CLI and SDK attacked by strangers three times. Some of the worst bugs came from my own fixes. A developer ran three rounds of AI-agent code review against their npm-published CLI and SDK, having the agents install the packages fresh and try to break them. The first round surfaced six high-severity findings, including a Node 18 crash on every track() call and a flush() that returned early during concurrent flushes; the second round found that two of the three high findings were introduced by the developer's own round-one fixes, such as a beforeExit re-listen that kept a process alive for 67 seconds against a server returning 500s. By the third round no findings were rated high. A bug report lands: one file with a very long expression crashes the whole scan, with no file name and no summary. You add a guard, write a test with a file that crashes, watch it pass, close the ticket. How do you find out that the guard has a hole? Not from the test. The test is the exact file from the report, and the guard was shaped around it. That was my situation last week with a CLI that reads a codebase and lists the analytics events it can send, and the small SDK next to it that sends them. Both are on npm, both had a few hundred tests, and I was fairly sure they were in decent shape. So I asked for a different kind of check: three reviewers who had never seen my code or my reasoning, each starting from the README and the published packages, installing them into empty folders the way a stranger would, and trying to make them misbehave. They were AI agents, which is the only way I could afford three of them for a whole afternoon, and I told them to distrust my tests and my changelog. I did it three times. After each round I fixed what they found and sent a fresh set at the new build. The first round found what you'd expect from a tool that grew by examples. A script on Node 18 threw on every track because that version has no global crypto . An SDK flush returned instantly when another flush was already running, so a serverless function could end before anything was delivered. An object I'd passed in, then changed later, could make a background send crash the process. A broken value in the browser's saved queue made the constructor throw on every page load. On the CLI side, a template-heavy Astro page killed the whole scan, and with no plugin installed a run quietly overwrote a catalog of 68 events with 2. Boring, real, fixed in an evening. The count of findings the reviewers rated high was 6. The second round is the reason for this post. Two of the three high findings were things I had added in round one. The exit that wouldn't happen. A Node script that ends with events still queued should send them and go away. I'd noticed that Node fires beforeExit every time the event loop drains, and a script can track more events after the first drain, so I made the SDK listen again each time. It felt careful. Then a reviewer pointed an SDK at a server that always answers 500 and let the script end normally. With a one-second shutdown budget, the process stayed alive for 44 seconds. With the default ten seconds, 67. Every time the loop drained, the SDK tried again, failed again, and drained again. My careful change turned "give up quietly at exit" into "retry until the circuit breaker opens". The fix was one condition: listen again only after a flush that delivered everything. The reviewers' next round found a second route into the same problem, a timer that started its own flush behind the exit flush and ran without the exit limit. I had fixed the symptom I was shown and not the property I cared about, which is that exit is bounded. The stack overflow I "fixed". The original crash was a stack overflow in my own code walking a file with a very long "a" + "a" + ... chain. I rewrote the walk to use an explicit stack, added a guard that skips files nested too deeply, and exempted left-leaning binary chains from the depth count, because the TypeScript compiler handles + chains in a loop and I'd tested 12,000 terms fine. A reviewer wrote the same shape with && : 800 operands, whole scan dies, no file named. TypeScript's binder recurses on logical operators. My exemption said "binary chains are flat", and that was true for the one operator I had tried. The honest version of that lesson is small. I generalized from one case, and the test I wrote for the guard checked only the case that had already crashed. High-severity findings in round two: 3. By the third round the reviewers rated nothing high, and two of the interesting findings were again mine. One is worth telling because it's about a dependency. The Astro plugin hands your page to Astro's own compiler, which is a Go program compiled to WebAssembly. On big pages it runs out of memory and dies hard, taking whatever else was running with it. After round one I'd added a check that looks at a page's markup first and refuses to feed it the pages likely to kill it. In that check I counted a missing end tag the way HTML does. In HTML,