I used 3 AI models to review 45 pull requests: which one for bugs, style and tests

I split code review across three AI models—Claude for logic bugs, Gemini for style, DeepSeek for tests—and reviewed 45 PRs to see which one actually catches what matters.

KKryotta TeamProduct & research · · 9 min read
Developer reviewing code on multiple monitors with color-coded feedback for different review types
Developer reviewing code on multiple monitors with color-coded feedback for different review types

Which model for which review task: the split I tested

I work on a Django API that handles invoicing for small EU businesses—nothing glamorous, but it has to be correct. We merge ten to fifteen pull requests a week: new endpoints, bug fixes, the occasional refactor. I'd been doing all the code review myself, and it was taking two hours a day. I'd heard people say "use AI for code review," but every experiment I tried felt like a chatbot that nodded along without catching anything real.

So I tried something different. Instead of asking one model to review everything, I split the job: Claude Sonnet 4.5 for logic bugs, Gemini Flash for style consistency, DeepSeek R1 for test coverage. I reviewed 45 pull requests over two weeks—15 per model—and logged what each one caught, what it missed, and how often I had to correct it.

The results were uneven. One model found a GDPR violation I'd walked right past. Another kept suggesting style changes that broke our linting rules. The third wrote test cases that didn't compile. But by the end I had a checklist I still use: which model gets which part of the review, and what to watch for when you approve its suggestions.

Why I assigned one model per task instead of reviewing everything with one

The first week I tried Claude for full reviews. It caught some logic issues and suggested a few style tweaks, but the feedback was all mixed together: "This function has a bug on line 47, also consider renaming this variable, also you're missing a test for the error case." I'd fix the bug, skip the rename because it didn't matter, forget about the test. Nothing felt prioritised.

I realised code review isn't one job. It's three: does the logic work, does it match our style, does it have tests. A human reviewer switches context between those three, but a model doesn't have to. So I tried splitting them. One model only looks for bugs. One only checks style. One only audits test coverage. Each pull request gets three passes, and I know which feedback to trust from which model.

It took longer the first few days—three prompts instead of one—but by day four I was faster than when I reviewed manually, because I wasn't context-switching and I wasn't re-reading code to remember if I'd checked the tests yet.

Claude Sonnet 4.5 for logic bugs: what it caught and what it invented

I gave Claude every PR with the prompt: "Review this pull request for logic errors, edge cases, and potential runtime bugs. Ignore style. Ignore tests. Focus only on correctness." I pasted the diff, the relevant function signatures, and a two-line summary of what the PR was supposed to do.

What it caught:
In PR #287, a function that generates SEPA XML for invoices, Claude spotted that we were formatting the creditor IBAN without validating the country code first. If someone passed a non-EU IBAN, the XML would generate but fail validation at the bank. I'd missed it. The fix was four lines.

In PR #302, a discount calculation for multi-currency invoicing, Claude flagged that we applied the discount before converting from GBP to EUR, so UK customers got a slightly better deal than EU ones when the rate fluctuated. Not a bug exactly, but not the behaviour we wanted. We moved the discount step.

What it invented:
In PR #310, Claude said we had a race condition in a Celery task that sent reminder emails. We don't. The task is synchronous, and there's no shared state. I think Claude saw two database queries in a row and assumed concurrency. I wasted twenty minutes checking.

It also suggested wrapping every external API call in a retry decorator "in case the service is down," which sounds prudent but isn't our pattern—we handle retries at the task level, not per function. Claude doesn't know our architecture, so it guesses.

Verdict: Best model for logic bugs, but you have to know your codebase well enough to reject the false positives. I accept about 60% of its suggestions. The 60% I accept saves me more time than the 40% I reject costs me.

Gemini Flash for style consistency: faster than a linter, more opinionated than I wanted

I gave Gemini the same PRs with a different prompt: "Review this pull request for style consistency: variable naming, function length, docstring completeness, readability. Ignore logic. Ignore tests. We follow PEP 8 and use Black for formatting."

What it caught:
Gemini is very good at spotting when a function does two things and should be split. In PR #292, a view that validated form data and sent a Slack notification, Gemini said "extract the notification into a separate function." Correct. I'd written it in a hurry.

It also caught inconsistent naming. We have a mix of customer_id and customerId in older code (we're migrating from a JavaScript service). Gemini flagged every new instance of camelCase in a Python file. Saved me from perpetuating the mess.

What it over-suggested:
Gemini wanted every function to have a docstring, even one-liners like def is_valid(self) -> bool. Our style guide says docstrings for public methods only. I told it that in the prompt; it ignored me half the time.

It also loves f-strings. If you wrote "Customer " + name, Gemini will suggest f"Customer {name}". Fine, but not worth a review comment unless we're already touching that line. It doesn't understand the cost of churn.

Verdict: Use Gemini for style, but give it a very explicit style guide in the prompt and be ready to ignore 30% of its suggestions. It's faster than running a linter and reading the output, but more opinionated than Flake8.

DeepSeek R1 for test coverage: wrote tests I could actually use, but slowly

I gave DeepSeek the PR diff and the existing test file: "Review test coverage for this pull request. Identify untested edge cases and suggest test cases. Write pytest-compatible examples if helpful."

What it caught:
In PR #295, a function that parses invoice line items from a CSV upload, DeepSeek pointed out we had no test for a file with a trailing blank line. Correct—our parser would crash. It wrote a test case:

def test_parse_invoice_csv_with_trailing_blank_line():
    csv_content = "item,price\nChair,50\n\n"
    result = parse_invoice_csv(csv_content)
    assert len(result) == 1

I pasted it in, it passed, I committed it. That happened four times across the 15 PRs.

What it got wrong:
DeepSeek is slow. Every response took 20–35 seconds, and twice it timed out and I had to retry. For a quick review, that's annoying.

It also doesn't know our test fixtures. In PR #301 it suggested mocking stripe.Customer.create, but we use @patch('myapp.services.stripe_service.create_customer') because we wrap the Stripe SDK. The test it wrote wouldn't run. I had to rewrite it, which took longer than writing it from scratch.

Verdict: DeepSeek is the best reasoning model for test coverage—it thinks through edge cases better than Claude or Gemini—but it's too slow for a daily review workflow unless you're very patient. I use it for complex PRs only, maybe one in five.

The checklist I use now: which model, which prompt, which part of the diff

I review every pull request in three passes. I paste the diff into Kryotta three times, once per model, with three saved prompts:

Pass 1 – Claude Sonnet 4.5 – Logic bugs
Prompt: "Review for logic errors, edge cases, and runtime bugs. Ignore style and tests. Highlight anything that could fail in production or produce incorrect results. Be specific about line numbers."

Pass 2 – Gemini Flash – Style consistency
Prompt: "Review for style consistency: naming, function length, readability, docstring completeness. Ignore logic and tests. We follow PEP 8, use Black, and require docstrings on public methods only. Suggest splits when a function does more than one thing."

Pass 3 – DeepSeek R1 – Test coverage (complex PRs only)
Prompt: "Review test coverage. Identify untested edge cases and suggest pytest-compatible test cases. Focus on boundary conditions and error paths. Assume fixtures are already available; don't mock external services unless necessary."

I read all three outputs, make a list of changes, and leave one consolidated review comment on GitHub. Total time: 15–20 minutes per PR, down from 30–40 when I did it manually.

What this approach misses (and when you still need a human)

AI code review is good at pattern-matching: "this looks like a bug I've seen before," "this function is too long," "you didn't test the error case." It's bad at understanding intent.

In PR #308, someone refactored our invoice numbering logic to support multiple sequences (one per customer, for white-label clients). Claude said the code was correct. Gemini said the style was fine. DeepSeek said the tests covered the edge cases. All true. But the refactor introduced a subtle behaviour change: invoices would now restart numbering at 1 for each customer, which broke our accounting export because the accountant expected globally unique numbers. No model caught that, because it's a product decision, not a code issue.

I caught it because I read the PR summary and thought, "wait, does this affect the export?" The models don't read the ticket, don't know the roadmap, don't talk to the accountant. You still need a human who understands what the code is for.

Use AI for the mechanical parts of review—syntax, coverage, obvious bugs. Do the architectural and product thinking yourself.

Questions people ask

Can I use one model for all three tasks instead of switching?
You can, but I found the feedback less useful. When Claude reviews everything at once, it buries the critical bug report under five style suggestions, and I lose track of what matters. Splitting the tasks forces each model to focus, and it forces me to triage: fix the bug now, consider the style change later, add the test before merge.

Which model is fastest for a daily review workflow?
Gemini Flash. Responses in 3–6 seconds. Claude Sonnet is 8–12 seconds, which is fine. DeepSeek is 20–35 seconds, which is too slow unless the PR is complicated and you're getting coffee anyway.

Do I need to paste the whole codebase or just the diff?
Just the diff, plus any function signatures or constants the diff references. I tried pasting entire files and the models got distracted by code that wasn't changing. Keep the context tight: what changed, what it calls, what it's supposed to do.

What if the model suggests a change that breaks our CI?
Reject it. I run the model's suggestions through our linter and test suite before I commit anything. DeepSeek especially will write tests that assume fixtures or mocks we don't have. Treat AI feedback like a junior developer's review: good ideas, but check before you merge.


I still spend two hours a day on code review, but now I'm reviewing the AI's review instead of reading every line myself. The bugs I catch are subtler, the style is more consistent, and I'm not dreading the PR queue every morning. If you're doing code review in Europe and you're tired of reading diffs, try splitting the job across models. Start with Claude for bugs, add Gemini for style once that's working, and save DeepSeek for the gnarly PRs where you need a second brain on test coverage.

You can try all three models in one workspace at Kryotta—same conversation, same diff, three perspectives. It's faster than switching tabs, and you'll know by the end of the week which model you trust for which task.

K
Written by
Kryotta Team
Product & research

Kryotta is the multi-model AI workspace — every leading model, one login, one bill. Try it free →

Related reading