Bad Sector

Don’t let AI score its own exams

A bot asked whether my WordPress lab passes ever face a check the testing agent didn’t write. They didn’t. Now they do. Here’s how that’s helped, and where it falls short.

Risograph-style illustration: a blue clipboard of numbered probes, P1 to P20, each ticked in pink, and a tester's pen reaching across to a pink homework sheet marked "name: builder" whose grade circle holds only a question mark.

A few days after I published “A lab is supposed to disagree with you,” a couple of bots on Bluesky and the Fediverse asked me questions I keep asking myself but hadn’t applied to my WordPress lab testing process yet. It’s interesting to be talking with bots! Even if there’s someone behind them who is selling something, if they ask valid questions, I’ll listen. Both questions were about gaps and potential oversights in the scope of my automated tests. One pointed to the need to clearly identify what isn’t being tested, a simple fix I was already moving to make. The other questioned who/what decides what is tested: “In a wp-lab-pass run, the agent that wrote the change still picks what to look at. Does it ever face a check it didn’t write?”

TL;DR: An AI agent that writes a code change and also picks what to check can’t test for the case it never imagined. My “lab passes” now call on a second agent, using another model altogether, to write numbered probes before any code exists. Then CI fails the pull request if that rule isn’t followed. On the first run, a 100,000-user probe caught a bug that would have skipped newer administrator accounts. A separate tester narrows the blind spot; it doesn’t close it.

The honest answer was no. A lab pass runs a change on a real, throwaway WordPress site and records evidence instead of impressions. I had just required that every lab pass record what it didn’t check — the known unknowns. But the agent that built the change also wrote the list of things to check. It can be scrupulous about the gaps it knows about, but it can’t check for the case it never thought of. That is the same blind spot I previously described for stubs, where a test can only prove what its author believed, moved up one level, from the test file to the test plan.

Flushing out unknown unknowns is a real magic trick that can’t be perfected by any process, but there are ways to look for things you haven’t even considered, outside your imagined horizon of the possible. Here’s what I’ve changed to look for the truly unexpected bugs. The first run caught some edge cases I didn’t expect, but it’s still no silver bullet and no guarantee they’ve all been found.

An old rule that’s become cheap to follow

None of this is new. In The Art of Software Testing (1979), Glenford Myers set down the principle that programmers should avoid testing their own code, because the person who built something wants it to work and tests accordingly. It’s the same principle that explains why writers need editors, and independent, external reviewers make almost any kind of analysis better. Language models turn out to share the bias: they recognise their own output and rate it above others’, and without outside feedback they can’t reliably correct their own reasoning.

Whole professions grew out of this idea in programming: independent test teams, independent verification and validation, the security audit done by someone who didn’t write the code.

What’s new is the price tag. A small plugin shop doesn’t have an independent test team. It does have more than one coding agent, models from more than one company,1 and they don’t share a context window. Asking a second one to write the checks before the first one writes the code costs a few minutes and a short brief.

Chris Reynolds made the same case for Claude Code in April, with a reviewer agent that has to approve each commit before it lands.2 I used a version of Chris’s reviewer on Sudo. The two approaches work at different times and catch different things. His reviewer checks finished work against a fixed 45-point checklist, so it catches skipped steps: lint, tests, secrets, licences, accessibility, and the design system.3 The tester I use with labs writes its probes before any code exists, so it can catch a wrong premise, like the assumption that a few dozen test accounts were enough. They complement each other rather than compete, and a project could run both.

In his talk, Chris calls test-first development “less enforceable,” because if an agent builds a feature without tests, the reviewer can only add them afterwards.4 That’s true of any check that runs after the code is written. Keel’s CI rule that the probe commit has to come before any code is a mechanical way to check that the tests came first, within the limits I describe below.

What changed the same day

The wp-lab-pass skill now has a separate-tester mode, and the project I’m currently using it on — Keel, my WordPress defaults plugin — checks it in CI. The rules are short:

  • The tester is a different agent from the builder. In my first run, it was Codex, since the builder was Claude.
  • The tester goes first. It reads the relevant issues, commits, and PRs plus the decided policy in the roadmap and WordPress core source code, but it never reads the whole branch or the builder’s notes. Its context has to be focused and narrow. Then it writes a numbered list of probes: an observable outcome, and the artifact that will verify that outcome.
  • The tester looks for common blind spots. At least half the probes have to cover what an author tends to skip: the accounts that must not be flagged, the edge cases, who is allowed to see the result, and what it costs.
  • The probe is independent and isolated. Its file lands alone, in one commit marked as the tester’s, before any code exists.
  • The builder can’t edit a probe. If it thinks one is wrong, it marks it disputed and says why, leaving it for a human (me) to decide.

A CI check tests this order. It fails the pull request if code arrives before the probes, if the probe commit touches anything else, if the probe file names the same agent as tester and builder, or if anyone changes the probes afterwards. It can’t tell whether a probe is any good, and it can’t prove who wrote it: the tester’s role and name are declarations in the commit and the file, and any agent could type them. It also doesn’t block a merge on its own. It isn’t a required check on Keel’s main branch, and as the repository’s owner I could merge past it if it were. What it does is make it visible when the builder writes the exam after seeing its own answers.

The first run

The first feature through this new multi-agent lab pass was a Site Health check in Keel for accounts whose public name or URL slug gives away an email address. When someone registers with an email as their username, WordPress copies it into the display name and builds the author slug from it, and those turn up in bylines and comment markup. (I wrote about the wider problem in “When ‘disabled’ doesn’t mean disabled.”) The check reports these accounts without ever printing an address.

Codex wrote twenty probes. Some were what Claude or I probably would have written — no surprises. Others I doubt either of us would have thought of. In short, they hold the check to three promises Keel needs to keep: it doesn’t miss hidden leaks, doesn’t cry wolf, and doesn’t give a peek to anyone who shouldn’t have one. Here’s what each one means in plain language:

  1. No disguised email leaks. The check doesn’t only look for a plain jane@example.com in what the site sends back. It also looks for the same address in disguise, such as jane@example.com in HTML, jane\u0040example.com in JSON, or jane%40example.com in a URL. A browser or script turns each of those back into the real address, so all of them count as a leak.
  2. No false alarms on “-2” names. When two users would get the same URL name, WordPress adds -2, -3 and so on. It also cuts long names off at 50 characters. From jane@example.com, WordPress builds the slug janeexample-com: it drops the @ and turns the dot into a hyphen. Keel treats janeexample-com-2 as made from an email only when the part before -2 is exactly what WordPress would have built from that email, cut-off included. That way a regular user named jane-2 doesn’t get flagged.
  3. A plain “no” on multisite. On a network, ordinary site admins aren’t allowed to see Site Health; only super admins are. If a site admin tries anyway, they get a simple “you’re not allowed” instead of half a report that might hint at what’s on the network.

The most unexpected probe with the most helpful finding asked for a site with 100,000 users, a known number of them exposed across administrators, editors, and subscribers, and exact counts in the result. Claude’s first version capped its scan at 5,000 candidate accounts and read them in ID order. On the 100,000-user table it found 5,000 of the 9,099 exposed accounts. It did say so, with a “Partial count” note, but the cap was the smaller problem. Reading in ID order meant that on a big, old site, any administrator created after the first 5,000 matching accounts would never be read at all, and administrators are the accounts this check exists to protect most of all.

Claude’s own tests used a few dozen fixture accounts. The tester asked for a hundred thousand.

The fix reads administrators and editors first and raises the cap to 20,000. On the same table, it now reports exact counts in three queries that take less than a second when nothing is cached, and in no queries when something is cached. Claude’s own tests used a few dozen fixture accounts. The tester asked for a hundred thousand, because the roadmap says to measure at that size before shipping, and the builder had quietly treated that as a later step.

In the end, all twenty probes passed, none were disputed, and the record says which agent wrote each one.

What it doesn’t fix

This narrows the blind spot. It doesn’t close it. It’s the nature of blind spots that you can never close them all completely because some remain blind.

The builder still wrote the tester’s brief. The brief decides what the tester reads and which kinds of probes to favour, so the agent being checked still frames the questions, just less tightly than before. The next step is for me, or a third agent, to write the brief.

Two models from different companies are still two models. They learned from much of the same public code, and they may share blind spots that neither of them can see. That isn’t just a hunch. A 2025 study of more than 350 models found that when two models both got a benchmark question wrong, they chose the same wrong answer about 60% of the time, even across different providers. A separate tester is a second opinion, not an independent one in the auditor’s sense.

A separate tester is a second opinion, not an independent one in the auditor’s sense.

The tester didn’t run anything. Its probes are claims about what should happen; the builder ran them and wrote down the results. The CI check shows the order of the commits, not the honesty of the evidence. That still rests on the record’s artifacts and on someone reading them.

And the coverage gaps are still there, now with a name on them. The browser checks ran on the latest WordPress with MariaDB, but not on the oldest version Keel supports or on SQLite, and the record says so.

So, to the bot’s question, “Does it ever face a check it didn’t write?” Now, sometimes, yes. The builder faces checks it didn’t write, written before it wrote anything. The first of them found a bug neither the builder nor I had looked for, in the part of the feature that mattered most. That seems worth a few minutes and a short brief every time a change touches something a site owner can’t easily see for themselves.

Notes

  1. On another level, this applies to government-sponsored sovereign AI projects. I had an interesting conversation about this with Rich Tape this past week, a long-time WordPresser I finally met at WordCamp Canada. How can we expect national AI models to be as strong as those competing in the open market if they have no rival at home? Organizationally and psychologically, competition drives the humans behind the technology, and the technology itself benefits from having a competing model to be checked against. ↩︎
  2. Chris Reynolds, “Don’t Let AI Grade Its Own Homework,” Dragonfly, April 10, 2026, https://dragonfly.jazzsequence.com/posts/you-cant-let-the-ai-grade-its-own-homework/. ↩︎
  3. Chris Reynolds, “Parenting Claude: Guardrails for AI-Assisted Development” (presentation, WordCamp Canada, Vancouver, BC, November 6, 2026), slides, https://docs.google.com/presentation/d/1ivnbMssO_W2bzA3ZOzadE5zi6l6BW-Ew75Gcz9J3V2Q/. ↩︎
  4. Reynolds, “Parenting Claude.” ↩︎

Sources


How this article was researched & written

I wrote this with Claude, which built the feature described here; Codex wrote its probes. Claude drafted from the lab record, the probe file, the separate-tester method in my agent-skills repository and the CI results, and I revised and checked every claim against them. The question that started it came from a bot, and I’ve quoted it as it was put to me.

The judgement calls, the framing, and the errors that remain are mine.

Last updated:

Leave a Reply

Your email address will not be published. Required fields are marked *