Adversarial Plan-Gates on Pull Requests: The DevOps Practice That Caught 16 Flaws in One PR (And Split It Cleanly)
Standard code review has a systemic bias: it asks “does this work?” and, most of the time, it gets a “yes.” The reviewer is looking at code that has passed tests, passed linting, passed CI, and looks internally consistent. Confirmation bias closes the loop — a plausible-looking PR reads as a working PR — and things merge that should not have. This post is about a specific counter-practice we now run on non-trivial PRs: the adversarial plan-gate, a review pass whose job is to try to prove the PR will fail. It is grounded in a recent real example: PR #18 on one of our internal repos passed all normal checks, then hit an adversarial gate that surfaced 16 flaws and cleanly split the work into PRs #18a and #18b, both of which merged with materially higher confidence than the original would have.
What “Adversarial Plan-Gate” Means in Practice

The adversarial plan-gate is a review stage inserted between “PR passes CI” and “PR merges to main.” Its job is not to check whether the code compiles or the tests pass — CI already does that. Its job is to construct the strongest possible case that the PR will fail in production and force the author to address each item in that case before merge.
The gate typically operates on three artefacts:
- The PR description. What does the author claim the PR does?
- The diff. What does the code actually change?
- The tests. What behaviour does the author actually verify?
And it produces one artefact: a numbered list of ways this PR will fail, ranked by severity, each one accompanied by a specific failure scenario (input, state, expected wrong output). The author must respond to each numbered item before the PR merges. Responses fall into three categories: fixed in a new commit, dismissed with rationale, or accepted as a known limitation with a tracking issue filed.
The gate is adversarial in the sense that its default hypothesis is “this PR will break something.” The reviewer’s job is to be right about that hypothesis — to find the ways the PR will break. If they cannot find any, the PR merges. If they find some, the author fixes or splits.
The PR That Motivated This Post
The specific example: PR #18 on one of our internal tools, described as “P7.2: OpenAPI 3.1 spec + Swagger UI for the External API.” The PR was substantial — it introduced the OpenAPI specification file, wired up Swagger UI, added JSDoc-to-OpenAPI generation on a subset of endpoints, and added an authenticated route for the docs.
Normal review would have looked at the diff, noted that the tests passed, spot-checked that Swagger UI rendered locally, and merged. It would have looked fine.
The adversarial plan-gate surfaced 16 distinct flaws. Some were subtle (an OpenAPI schema field that would parse but that Swagger UI would render as a broken enum). Some were dangerous (authentication middleware applied to the docs route but not to the raw OpenAPI JSON, so the schema could be scraped even from a locked-down deployment). Some were architectural (the JSDoc-to-OpenAPI generation coupled the doc format to the code format in a way that would make schema evolution painful within six months).
The right response was not to fix all 16 in the same PR. It was to split. PR #18a became “OpenAPI spec + auth on both the docs route and the raw JSON, with the safe subset of the endpoints already implemented,” which merged cleanly. PR #18b became “JSDoc-to-OpenAPI generation with the deliberate coupling decision documented as a constraint,” which merged after further discussion. Both PRs were smaller, safer, and reviewable in a fraction of the time #18 would have taken to review carefully.
Why This Practice Isn’t Standard
Most engineering teams do not run adversarial plan-gates on PRs, and the reasons are honest:
It feels rude. A reviewer whose job is to argue that the PR will fail can come across as combative or blocking. Cultural conventions in most engineering teams strongly bias toward “I trust the author, the tests pass, LGTM.” Explicitly running an adversarial gate is a cultural intervention as much as a technical one.
It looks slow. A PR that goes through an adversarial gate takes hours longer than one that gets a rubber-stamp LGTM. In teams under quarterly-throughput pressure, the calculus looks bad — until you count post-merge incidents, which the adversarial gate prevents. Ironically, the adversarial gate is faster on any timescale longer than two weeks because it front-loads the failure detection.
It requires a different skill. Constructive review (“this looks good, here’s a small nit”) is comfortable and universal. Destructive review (“here are the seven ways this will fail on the third Tuesday of a month with 31 days”) requires a different mindset. Not every reviewer wants to do it. That is fine — you only need one or two reviewers who can, gated on the PRs that matter.
Nobody trains it. Universities teach students to build. Corporate training teaches engineers to review politely. Almost nobody teaches the specific mental move of “construct the strongest possible case that this fails.” It has to be learned deliberately.
The Structure of an Adversarial Gate

For a team that wants to try this, the mechanical structure that works for us:
1. Only gate the PRs that warrant it. Not every trivial bug fix needs an adversarial pass. The gate applies to any PR that:
- Touches a critical write path (payment, auth, data mutation on user-owned tables).
- Modifies infrastructure or CI configuration.
- Introduces a new external dependency.
- Changes a database schema.
- Modifies more than 300 lines across 5+ files.
2. Document the gate as a required review stage. Add an explicit “Adversarial Review” section to your PR template. Force the reviewer to fill it in before approving. The prompt for the reviewer is simple: “List every way this PR will fail in production, ranked by severity. For each item, give the specific input or state that triggers the failure.”
3. Distinguish gate output from normal review comments. Adversarial findings are numbered, labelled, and tracked separately from nits and style suggestions. This makes it clear to the author which items are load-bearing.
4. Author responds to each numbered item. Three response types are valid: fixed in <commit sha>, dismissed because <reason>, or accepted as limitation, tracked in <issue #>. No numbered item is allowed to be silently ignored.
5. Merge gate. The PR cannot merge until every adversarial item has a response and the reviewer has explicitly approved the responses. Not “approve the PR overall” — “approve the response to each item.”
What Changes When You Do This
Three measurable outcomes we have seen from consistently running adversarial gates on non-trivial PRs:
Post-merge incident rate drops sharply. The bugs that would have shipped are found in the gate and fixed before merge. This is the primary payoff. Anecdotally, we have seen a 4-6x reduction in the “rolled-back within 48 hours” rate on PRs that go through the gate.
Large PRs get split more often. The gate surfaces the true scope of what the PR is doing. A “small refactor” PR that turns out to have 12 semantic changes touching auth, billing, and lobby routing gets split into three or four smaller PRs, each one reviewable on its own merits.
Team engineering discussion improves. The gate output is documented, reviewable, and often surfaces genuinely interesting architectural questions. A PR whose gate output includes “this coupling between the doc format and the code format will make schema evolution painful within six months” starts a conversation the team should be having anyway.
The Skill and Tooling Side
There are two ways to run an adversarial gate: a human doing it manually, or an automated pass (LLM-driven) generating the initial findings for a human to curate.
Manual is where you start. A senior engineer with the right mindset can run this practice for a small team without any tooling. The mental habit is the load-bearing part.
Automated adds throughput once the practice is established. We have been building and shipping a Claude Code skill (claude-adversarial-qa-skill on GitHub) that codifies the adversarial-review prompt into a reusable slash-command. The skill produces a first draft of the numbered findings list; the human reviewer curates it (deletes false positives, adds items the LLM missed, ranks severity). This roughly doubles the throughput of the practice — a senior engineer can adversarially-review two or three PRs an hour instead of one.
The skill itself is open source. If you want to try running adversarial gates without adding meeting time to your team’s calendar, an LLM-assisted first pass is a low-risk way to start.
For Australian Small Businesses Running Their Own Engineering
If your Sydney-or-anywhere Australian SMB runs its own internal engineering, the adversarial plan-gate is one of the highest-leverage process changes you can make. It costs almost nothing to introduce — no new tooling required to start, no vendor spend, no headcount. It saves real money by preventing production incidents that would otherwise consume a founder’s or engineering lead’s time.
The practical rollout for a small team:
- Week 1: Add “Adversarial Review” section to your PR template. Do not enforce yet.
- Weeks 2-3: One senior engineer runs the gate on 3-5 PRs. Observe what it catches. Adjust the prompt.
- Week 4: Make the gate mandatory on PRs matching the criteria in section 4 above.
- Week 6: Retrospective. Measure incident rate. Adjust criteria.
- Ongoing: Consider adding an LLM-assisted first pass to scale the practice without adding senior-engineer time.
The practice compounds. Every adversarial finding is also a learning artefact for the whole team — the six-month collection of adversarial findings on your team’s PRs is a diagnostic snapshot of your team’s most-common failure modes. That snapshot is worth more than any generic “engineering excellence” workshop your team will ever attend.
Related Reading
- The Expand/Contract Primary Key Migration: Rolling-Deploy-Safe Schema Changes on Postgres — a specific example of a migration that a normal review would rubber-stamp and an adversarial pass would catch (the ON CONFLICT clause failure during rolling deploy).
- Mautic /api/contacts 500 After Upgrade: The Doctrine Hydration Bug Nobody Warns You About — an upgrade bug that would have been caught by an adversarial pre-merge review of the “add is_index column” migration.
- The og:image Gap: Why Half the Web Shares Its Pages With No Preview Card — a class of hygiene error that adversarial gates on your deploy pipeline catch before ship.