278 Bugs. 15 Modules. One Thesis.
What happens when you turn your own multi-model review pipeline against itself.
We built a multi-model review pipeline. Then we pointed it at itself. Not the output — the source code. Every module. Every function. Every trust boundary.
Four CC subagents with calibrated adversarial mindsets run first. Then the code goes to web LLMs that have never seen the codebase. Round after round until every model says “ship it.”
278 bugs. 15 modules. 97 rounds. 1,553 tests.
The campaign
Thirteen modules audited over four days, plus two pre-campaign modules from a week earlier. Every module got the same treatment: four parallel CC subagents (Security Adversary, Correctness Pedant, Architecture Critic, Test Coverage Hunter), then web LLM rounds with Claude and ChatGPT until convergence.
| Module | Lines | Rounds | Bugs | Tests |
|---|---|---|---|---|
| cli.py | 2,550 | 12 | 45 | 74 |
| state.py | 2,213 | 11 | 45 | 54 |
| patch.py | 320 | 16 | 32 | 70 |
| prompts.py | 1,658 | 9 | 26 | 80 |
| triage.py | 2,277 | 6 | 26 | 28 |
| github.py | 400 | 7 | 21 | 34 |
| ts_scope.py + diff.py | — | 5 | 14 | 20 |
| runner.py | 2,203 | 7 | 11 | 10 |
| dedup.py | 320 | 8 | 11 | 31 |
| batch.py | 220 | 2 | 11 | 15 |
| sarif.py | 216 | 2 | 10 | 30 |
| eval/harness.py | 760 | 2 | 9 | 27 |
| types.py | 240 | 2 | 3 | 33 |
| chunk.py | 187 | 2 | 2 | 22 |
| generate-intake.sh | — | 6 | 12 | — |
| Total | — | 97 | 278 | 528 |
Sorted by bug count. The largest modules produced the most bugs, but not proportionally — patch.py at 320 lines produced 32 bugs across 16 rounds. dedup.py, a dormant module with zero production catches, needed 8 rounds and produced 11 bugs.
The model that wouldn't stop finding bugs
The campaign ran Claude and ChatGPT in parallel every round. The pattern was consistent: Claude converged early, ChatGPT kept finding real bugs.
github.py: Claude PASSed Rounds 4–7. ChatGPT found a real P2 in every one.
dedup.py: Claude PASSed Rounds 1–7. ChatGPT found a real bug in every one.
In github.py, ChatGPT found the non-string field rendering crash (Round 4), the line-zero API bug (Round 4), the corroborated_count type mismatch (Round 5), and the argv-size fragility in comment posting (Round 6) — all while Claude declared the module “production-ready.”
In dedup.py, ChatGPT peeled back seven consecutive layers of the same bug class: type guards on untrusted data. Same-reviewer comparisons, unsanitized prompt injection, malformed container shapes, synthetic ID collisions, invalid ID types, alias validation, and canonicalization gaps. Each round Claude PASSed. Each round ChatGPT found the next layer.
This is the thesis of Sweep playing out on Sweep itself. Ensemble diversity catches what same-model reviewers miss. Not because one model is smarter — because they have genuinely different blind spots. That's Mode 3.
The bug class that wouldn't die
One bug class appeared in every module: non-string values from LLM output reaching code that assumes strings. A finding with {title: 42} crashes .replace(). A classification with {classification: ["ADOPT"]} passes key-presence checks but fails string comparison. An ID of None maps to an empty-string dict key, silently overwriting prior entries.
The fix is always the same: validate type at the trust boundary, not just presence. Pydantic TypeAdapter for structured fields. A simple _safe_text() coercion helper for rendered fields. The pattern was so consistent that we codified it as gotcha #71.
The lesson: LLM output is not just “might be wrong.” It's “might be the wrong type entirely.” Every .get() that assumes the value is a string is a crash waiting for a model having a bad day.
The modules that surprised us
patch.py — 16 rounds, 32 bugs
A 320-line module that generates patches from adopted findings. It took more rounds than the 2,277-line triage module. Fence validation, identity checks, whitespace preservation, all-skipped accounting — the surface area was deceptive. Lines of code is not a predictor of audit complexity.
github.py — almost skipped
We nearly moved on to the next module after Round 3. The commit said “shipped v2.20, 3 rounds, 17 bugs.” Then we fed Round 4 to web LLMs. ChatGPT found 2 more P2s. Then 1 more. Then 1 more. Final count: 7 rounds, 21 bugs. The module that was “done” needed four more rounds.
dedup.py — 299 lines, dormant, 8 rounds
Zero production catches. The module exists as cheap insurance in case the lead model's dedup logic ever fails. It produced more audit rounds than types.py, eval/harness.py, sarif.py, chunk.py, and batch.py combined. Dormancy doesn't mean correctness.
The final numbers
| Modules audited | 15 |
| Total rounds | 97 |
| Bugs found and fixed | 278 |
| Regression tests added | 528 |
| Total tests passing | 1,553 |
| Gotchas documented | 72 |
| Versions shipped | v2.11 → v2.25 |
| Duration | 4 days |
528 regression tests means every fix has a test that prevents re-introduction. Seventy-two gotchas means every non-obvious design constraint is documented. The test suite went from 1,121 to 1,553. The source-to-test ratio is now roughly 1:1.
Before and after
Same artifact. Same four reviewers. Same lead model. The only difference: 278 bug fixes. We ran the full pipeline on the same codebase review that produced the v20 eval baseline, then compared.
| Metric | v20 (before) | v25 (after) | Change |
|---|---|---|---|
| Total adopted | 69 | 40 | -42% |
| Adopt rate | 52% | 29% | -23pp |
| Dismiss rate | 18% | 54% | +36pp |
| P1→P2 downgrades | 0 | 3 | new |
| ALREADY_ADDRESSED (P2) | — | 28 | new |
| Eval pass rate | 100% | 100% | — |
| P1 precision | 100% | 100% | — |
| QWK | 1.000 | 1.000 | — |
Adopt rate collapsed from 52% to 29%. Not because the pipeline finds fewer bugs — because the bugs are already fixed. Twenty-eight findings were dismissed as ALREADY_ADDRESSED in Pass 2 alone. The verification gate caught 3 overpromoted P1s and auto-downgraded them to P2.
Reviewer signal shift
| Reviewer | v20 signal | v25 signal |
|---|---|---|
| deepseek | 73% | 27% |
| gpt-nano | 75% | 27% |
| grok | 42% | 62% |
| gemini-lite | 21% | 17% |
| lead (grok) | 62% | 69% |
DeepSeek and GPT-Nano dropped from 73–75% signal to 27%. Not because they got worse — because they keep finding the same issues, and now the lead correctly dismisses them as already fixed. Grok's signal increased from 42% to 62%: it finds fewer but more novel issues that survive the stricter triage. The lead's own signal rose from 62% to 69%.
Severity calibration
| Severity | v20 (90) | v25 (138) | Trend |
|---|---|---|---|
| P0 | 17 (19%) | 15 (11%) | ↓ |
| P1 | 10 (11%) | 35 (25%) | ↑ |
| P2 | 50 (56%) | 63 (46%) | still dominant |
| P3 | 13 (14%) | 25 (18%) | ↑ |
Confusion matrix (v25): perfect diagonal.
P0:15 · P1:35 · P2:63 · P3:25 — zero misclassifications · QWK 1.000
P0 share dropped from 19% to 11%. P1 share rose from 11% to 25% — but with 100% precision and 3 auto-downgrades. The verification gate is doing its job: every P1 that reaches the changelog has code-path evidence. The confusion matrix is a perfect diagonal. No severity was ever misclassified.
The pipeline reviewing the pipeline
The methodology was designed to produce Mode 3 behavior: genuine independent discovery, not anchored verification. The campaign validated that this works — not just on spec documents and student code, but on the review pipeline's own source.
ChatGPT found bugs that Claude missed, round after round, in the same codebase. Not because one is better. Because they're different. That's the entire point. One model reviewing its own work converges to its own blind spots. Multiple models converge to the actual bugs.
278 bugs in code that was already working, tested, and production-deployed. Every one of them found by a model that had never seen the code before that round.
Independence is structural, not instructional.