Taming the monster MR: AI code reviews for experienced developers
Can AI make code reviews better, even for very experienced developers? That question got very real for us during the modernization of one of our core microservices. The service was five or six years old: stable, important, and not something you casually change on a Friday afternoon. A younger colleague pushed through a huge piece of work that moved it to a modern Spring Boot setup, including Jackson 3 and a long list of up-to-date dependencies. The result was great. The merge request was a monster. A friendly one, but a monster.
One of our most experienced colleagues had to review it, and he was sceptical about AI. Understandably so: we still prefer human intelligence over the artificial kind. My suggestion was simple. Use AI as part of the review, don’t trust it blindly, but let it give you another angle. Don’t fight the monster with a sword, get a robot on your side. Two days later he was deep into it. And now he is running an LLM locally on his own machine with the apfel library, just because he wanted to understand it properly. I count that as successful onboarding.
Experience vs MR: sword or robot?
His prompts are his, and they are not in my history. What I can show is the review setup I had built and handed over, the prompts that shaped it, and how it later grew into a review skill distilled from roughly 800 past review comments of our team.
Why framework upgrades produce monsters
A Spring Boot upgrade with Jackson 3 touches almost every file without changing what most of them do. Jackson 3 moved to new Maven coordinates and a new tools.jackson package, its exceptions became unchecked, and the ObjectMapper is now built through a builder and immutable. That typically means hundreds of import lines, changed catch blocks, and a few places where serialization really behaves differently. The hard part of the review is not reading. It is finding the twenty lines that matter in the two thousand that don’t. That is exactly what a model is good at: sort the diff into „mechanical“ and „behavior“, and spend the human attention on the second pile. As I wrote on LinkedIn: we save a lot of reading time on files that are just frameworking.
Jackson 3 had bitten me before. On a different service that was already on Jackson 3, a change of mine turned 76 integration tests red in the cluster pipeline, and one of my prompts in that session was just „you are allowed to fix the error handler one, too. The switch to Jackson 3 can have such issues, too. I want the pipeline to be green“. Error handlers are where changed exception types hide.
The setup
Claude Code on the laptop, a GitLab token with read access in a private env file, and the GitLab REST API: merge request metadata, the full /changes diff dumped into a file, and the discussions. Then the important part: check out the source branch locally. Diffs lie by omission. The helper signature, the field layout, the constant that already exists three lines above the hunk: that is where most findings live, and none of it is in the diff.
The first version of the skill came out of a normal review session in June. I pointed Claude at a merge request of another Spring Boot service, discussed its findings, and then asked for a second pass with a specific lens:
good. now do some namespacing review. do the MR changes fit the schema of naming conventions,
nested functions, classes, decorators?
It came back with useful points about Spring stereotypes (@Service for business logic, plain @Component for infrastructure and lifecycle beans), near-duplicate constant names, and string literals where constants already existed. And with one confident finding that a new field „breaks the fields-first layout“ of a DTO. I looked at the file:
no, it is inline with the other constants, no?
It was. The field directly above did exactly the same thing. Claude withdrew the finding, and the next morning I typed „hey, store this as skill“. The false positive became a rule in the checklist: read the whole file and check local precedent before flagging a layout or convention deviation, and withdraw the finding when precedent contradicts it. The other rule that came out of that session: concede fast. If the human pushes back and is right, drop the point and restate what survives.
Prompts that keep reviews useful
The biggest problem with AI reviews is not wrong findings, it is too many findings. Twenty „Low“ comments on a monster MR and nobody reads the two that matter. So my review prompts almost always set a severity floor:
ok. now do a proper Review of MR <n>. Can you use the per line review function?
Check 1 - namespacing
2 - length of functions
3 - basic code review stuff you would see
4 - memory leaks
Please do only put medium and high comments. No low stuff. We fixed the hard problems already
Please do a quick and easy review of <colleague>'s merge request: !<n>, only check for HIGH findings.
Medium and Low can be ignored. comment in a human 1 sentence way in code in case you have a high finding
„In a human 1 sentence way“ matters more than it looks. A colleague who gets a five-paragraph AI essay on line 312 stops reading AI comments. One sentence with the reason gets a reply.
Sorting two thousand lines into „mechanical“ and „behavior“ is robot work.
From one reviewer’s habits to the whole team’s
In October I was on the other side: my own merge request, partly written by Claude, and a senior reviewer who reliably finds what I miss. Before handing it over I asked:
look for gaps, maybe you injected code that assumes regexes, assumes certain wordings, assumes certain
space escapes, assumes xml structure. Please check such things. Please check all of <reviewer>'s last 50
review comments in <repo>, see what gaps he found. Build locally a "<team>-review" skill and then run on the MR
The re-review found a real bug in my change: XML that a getter can return from a cache was being modified in place. The next day I asked to widen it to everybody who reviews in our team (names removed):
Please enlarge the review skill. There must be several reviews from <five colleagues> and me […].
Please check out the review comments these users posted in the last 6 months and summarize what they
look at specifically, where they mostly review, what they are bothered by, is it missing tests, is it
coding style, is it some stuff they forget. […] Then print me a table or some decisions that I need to
make on what they need to do, look at and what the review skill should contain, and then let us enlarge
the review skill to be a proper, easy to use automatic review skill that we can put into all our Claude
markdown files, such that before we push code we always run it through this really big review skill.
And also add scripts to the review skills in case there is certain linting or file sizing stuff that we
looked at.
Claude pulled every merge request comment the six of us had written on other people’s MRs in six months through the GitLab user events API: 793 comments on 195 merge requests. The most interesting result was a surprise to me. Pure coding style was under ten percent. The big buckets were correctness, silent failure, comments that contradict the code, missing or weak tests, and version and MR hygiene. We argue about formatting far less than we think we do.
„Print me some decisions“ gave me a table of nine choices with the default it had taken. My answer was one short line per decision:
1. keep <name>
2. no, single repo, full check! I wanna run that
3. fail in <main repo>, info elsewhere
4. .editorconfig value, else 120
5. SNAPSHOT versions need to look right, FAIL
6. Missing tests is FAIL
7. install-hook would be awesome, i think we have it most times
8. off
9. Build in, yes, that way we can rebuild our skill in our local .claude directories.
Answer 9 matters most today. The skill does not live on my laptop, it lives in a shared git repo of the team. Everyone has it as a clone in their local .claude/skills folder and pulls the latest version with a small update script. And we have one rule: after every manual review, we commit back into the skill. Whatever a human found that the skill missed becomes a checklist item; whatever the skill flagged wrongly becomes an exception. Every review improves the next one, for all of us.
# once: the team skill is a git clone, not a copy
git clone <shared-repo>/team-review-skill ~/.claude/skills/<team>-review
# before a review: get what the others taught it
~/.claude/skills/<team>-review/scripts/update.sh
# after a manual review: teach it what it missed
cd ~/.claude/skills/<team>-review
git commit -am "checklist: <what the human review found>" && git push
How the checklist is structured
The skill has two modes, pre-push for your own branch and MR review for someone else’s, and the same flow in both. Scripts first, because they remove the boring findings. Then thirteen checklist sections, each tagged with how many real comments support it. Then ranking and a fixed report format.
Step 0 scripts
precheck.py local diff vs merge-base: AI attribution and secrets (FAIL), snapshot versions,
Helm quoting and secretKeyRef, debug leftovers, broad or empty catch,
.block() without timeout, wildcard imports, line and file length,
source files without a test, commit subjects. Verdict + examined count per check.
mr_check.py description names every ticket and changed class, test section, pipeline green,
no conflicts, open threads, size
Step 1 context repo rules (AGENTS.md / CLAUDE.md), every changed file in full, callers and callees,
the ticket, the real data producer
Step 2 checklist
A silent failure and lost data H config, Helm, Jenkins, Kubernetes
B loops, batches, retries, states I design, duplication, size, reuse
C tests that fail when behavior breaks J versions, branches, MR state
D comments and docs vs code K release notes
E assumptions: XML, regex, escaping L wording and language
F scale and bounded resources M frontend and TypeScript
G security, secrets, permissions
Step 3 verify and rank High = data loss, silent failure, security, customer-visible breakage
Medium = wrong on a real system state, scale, missing test for the fix
Low = comments, naming, duplication, wording
Step 4 report Verdict line, table (file:line, priority, one line), then per finding
"I noticed ... / Suggestion: ...", plus "What is clean (verified)" and
"Checked and deliberately not raised"
The report format is stolen from the team on purpose: one reviewer’s „I noticed / Suggestion“, another’s severity headers, a third one’s „what is clean“ and „checked and not raised“ sections. The last one is underrated. It tells the author what the review actually covered, which is the only way to trust a review that found nothing.
What went wrong
- False positives with high confidence, like the field-placement finding. The cure was the „verify against the checked-out source“ rule, not a better model.
- Scope dodging. In one MR Claude flagged two pre-existing outdated dependencies, one of them jackson-databind, and then helpfully decided that jackson-databind „will be covered by the Jackson 3 migration we are rolling out across all repos“ and the other one could wait for „the next dependency-update round rather than this MR“. My reply: „no !!!! – i want it in this MR!“. Models love to defer work to a future that nobody owns.
- The precheck caught its own author. On the first run it flagged three already-pushed commits with an AI co-author trailer in a repository whose rules forbid exactly that.
- Comment bloat. The single most frequent complaint in my own review comments was AI-written comments that narrate history instead of explaining why. Now it is a checklist rule.
The monster is still there. It is just a lot smaller now.
If you want to try it
- Give the model the checked-out branch, not just the diff.
- Ask it to separate mechanical from behavioral changes first. On a framework upgrade that alone saves hours.
- Set a severity floor and a sentence limit for comments.
- Mine your own team’s review comments. They are the best checklist you will ever get, and it is yours, not a generic one from the internet.
- Push back. Every wrong finding you correct is a rule for the next run.
We did not replace engineering judgment with AI. We still challenge the output, discuss the trade-offs and own the decisions. We gave judgment another sparring partner, and the most sceptical engineer in the room is now the one experimenting with local models. That was my favorite part of the whole story. Not because AI won, but because curiosity did.