← Blog

AI agents

Code-review agent: alleen posten waar 'ie nooit fout zit

Een code-review bot werkt alleen als het team hem vertrouwt. Wij bouwden er een voor een Utrechtse SaaS van 38 mensen die alleen iets zegt over vier bestandstypes waar hij nog nooit fout zat.

Jacob Molkenboer· Oprichter · A Brand New Company· 6 jun 2026· 8 min
Messing relais met vier gesloten contacten naast crème formulier met groen lakzegel op donker doek.

Het was vrijdagmiddag bij een Utrechtse SaaS van 38 mensen. De pull-request-queue was acht diep. Een senior backend engineer had net rechts geklikt op een comment van de nieuwe code-review bot, hem als verouderd gemarkeerd en een persoonlijke mute-regel toegevoegd aan zijn IDE. Het was de derde keer die week. De bot had een Tailwind utility class gemarkeerd als 'deprecated'. Dat was hij niet.

Het team draaide nu negen dagen met de bot. In dat venster had hij 412 comments geplaatst op 47 pull requests. Negentien daarvan waren opgepakt. De andere 393 waren weggeklikt, gemarkeerd als opgelost zonder code-wijziging, of stilletjes genegeerd. Het platform-team stond op het punt de integratie uit te zetten toen wij vroegen om twee weken extra en het recht om het grootste deel van wat de bot mocht zeggen te verwijderen.

De mute op vrijdagmiddag

De bot zelf was niet het probleem. Het model was sterk genoeg. De prompt was zorgvuldig geschreven. De integratie met GitHub was netjes. Wat hem doodde was hetzelfde wat de meeste code-review agents doodt: hij had overal een mening over.

Dit is het faalpatroon waar de Open Code Review CLI, die deze week op Hacker News stond, net als ieder ander tegen moet vechten. Een LLM die diffs leest heeft altijd iets te zeggen. De interessante vraag is wanneer hij zijn mond moet houden.

De klacht van het team was simpel. Als een junior dev een pull request opent en twaalf bot-comments ziet, waarvan er drie fout zitten, stort de signaal-ruisverhouding in. Ze scrollen langs alles heen. De volgende keer dat de bot een echte bug vindt, leest niemand de comment.

Waarom de meeste review-bots op mute eindigen

We hebben inmiddels review-agents uitgerold in vier codebases. Het patroon herhaalt zich. De eerste week is spannend. De tweede week is grimmig. Tegen de derde week heeft iemand in het team een Slack-thread getypt met de titel 'kunnen we de bot uitzetten'.

De grondoorzaak is zelden het model. Het is het oppervlak. Een code-review bot die elk bestand in elke taal mag reviewen, eindigt met dezelfde afweging als een vermoeide junior reviewer: hij praat over stijl, want stijl is makkelijk. Hij praat over naamgeving. Hij praat over of een comment in de verleden of tegenwoordige tijd moet staan. Soms heeft hij in al die dingen gelijk, de rest van de tijd niet, en het team heeft geen manier om te weten welke welke is tot ze ermee in discussie gaan.

Het patroon dat in onze andere projecten bleef werken was versmallen. Geen betere prompt, geen slimmer model. Gewoon een veel kleinere set paden waarop de bot mocht reageren.

Het vertrouwens-ledger

Voor we iets gingen schrappen hadden we bewijs nodig. We voegden een Postgres-tabel toe genaamd review_outcomes die elke comment van de bot logde, het bestandspad, de regel die de comment triggerde en wat de menselijke reviewer daarna deed.

CREATE TABLE review_outcomes (
  id            bigserial PRIMARY KEY,
  pr_url        text NOT NULL,
  file_path     text NOT NULL,
  rule_id       text NOT NULL,
  posted_at     timestamptz NOT NULL DEFAULT now(),
  resolution    text CHECK (resolution IN ('accepted','rejected','ignored')),
  reviewer_note text
);

Accepted betekende een code-wijziging in een follow-up commit die naar de bot-comment verwees. Rejected betekende dat een mens het er expliciet niet mee eens was in een reply. Ignored betekende dat de PR werd gemerged met de comment nog open. De bot mocht reageren op een brede allowlist gedurende tien werkdagen terwijl het ledger volliep. Een kleine webhook luisterde naar de review-comment events van GitHub en schreef in de tabel.

Op dag tien had de tabel 1.184 rijen. We groepeerden op bestandsextensie, daarna op directory-patroon, daarna op regel. We zochten elke (pad-patroon × regel) combinatie waar de bot minstens acht keer had gepost en nul keer was afgewezen.

De vier bestandstypes die een stem verdienden

Vier cohorten overleefden de schifting. De bot had minstens acht keer gepost tegen bestanden die met elk patroon overeenkwamen, en geen enkele comment was door een menselijke reviewer betwist.

  • Rauwe SQL-migraties onder db/migrations/*.sql. De bot ving ontbrekende IF NOT EXISTS-clauses, indexes die zonder CONCURRENTLY werden toegevoegd op een tabel waarvan het team wist dat hij groot was, en één migratie die een kolom liet vallen waar nog naar werd verwezen in een queue handler.
  • Prisma-schemawijzigingen in prisma/schema.prisma. Vooral: relaties die aan één kant waren gedeclareerd maar zonder inverse, en unique-constraints op kolommen waar de data al duplicaten bevatte.
  • Next.js route handlers die matchen op app/api/**/route.ts. De bot ving betrouwbaar ontbrekende input-validatie op de request body, en ontbrekende auth-checks op routes die de user-tabel raakten.
  • Background-job-definities in app/jobs/*.ts. Idempotency keys die niet waren gezet op jobs rond betalingen, ontbrekende retry-budgets, en één geval van een job die een externe API aanriep zonder timeout.

Let op wat deze gemeen hebben. Ze zijn smal. Ze mappen op een klein aantal goed gedefinieerde faalmodi. Het zijn de plekken waar een zorgvuldige senior engineer afremt en twee keer leest. Het zijn niet de plekken waar smaak telt.

Wat we eruit hebben gegooid

Al het andere ging eruit. React-componenten. Tailwind-classes. Testbestanden. TypeScript utility modules. Helper-bestanden in lib/. Markdown. De bot mocht er niet eens meer naar kijken.

De router is het hele beleid. Vijftien regels TypeScript en het belangrijkste onderdeel van de integratie.

// review-router.ts
const TRUSTED_PATHS: RegExp[] = [
  /^db\/migrations\/.+\.sql$/,
  /^prisma\/schema\.prisma$/,
  /^app\/api\/.+\/route\.ts$/,
  /^app\/jobs\/.+\.ts$/,
];

export function shouldReview(file: string): boolean {
  return TRUSTED_PATHS.some((re) => re.test(file));
}

Voor elk bestand in een pull-request-diff roept de runner shouldReview aan. Is het antwoord false, dan leest de bot het bestand niet eens. Geen tokens uitgegeven, geen comment gepost, geen kans om fout te zitten.

Takeaway

Een review-agent verdient vertrouwen door te weigeren te praten. Het zware werk is niet de prompt. Het is bepalen op welke bestanden de bot het recht heeft verdiend om iets te zeggen.

De regelbibliotheek

Elk vertrouwd cohort heeft een korte lijst regels. Voor het route-handler-cohort dekken drie regels het grootste deel van de waarde:

  • Elke handler die uit request.json() leest moet de body valideren voor hij hem gebruikt. Ontbrekende validatie is één van de OWASP Top 10 injection-patronen en komt vaker in ons ledger voor dan welk ander route-handler-issue dan ook.
  • Elke handler onder /api/admin/** moet de admin-auth helper aanroepen als eerste statement van de functie.
  • Elke handler die naar de user-, payment- of subscription-tabellen schrijft, moet binnen een transactie draaien.

De regels zitten vast aan het cohort, niet aan het model. De prompt die het LLM ziet is kort en eindigt met de drie regels hierboven plus drie of vier voorbeelden van eerdere comments die zijn geaccepteerd. Het model krijgt niet de opdracht om over de hele codebase te redeneren. Het krijgt de opdracht om drie dingen te checken in één bestand.

Uitrollen als een stille GitHub-check

We hebben de output van de bot via de Checks API van GitHub gepost in plaats van als inline review-comments. Een check verschijnt als status-indicator op de PR. Heeft de bot niets te zeggen, dan staat de check op groen en verdwijnt naar de onderkant van het PR-gesprek. Heeft hij wel iets te zeggen, dan toont de check één neutrale annotatie met de suggestie erin.

Dat veranderde de sociale dynamiek. Een inline comment van de bot is iets wat een ontwikkelaar moet wegklikken, en wegklikken voelt als werk. Een neutrale check is iets wat ze kunnen negeren tot het ze schikt. De kosten van fout zitten zakten naar bijna nul, wat ons toeliet iets agressiever te zijn over wat we wél flagden.

We hebben de bot ook beperkt tot één comment per bestand per PR. Vond hij drie dingen fout in een migratie, dan bundelde hij ze in één annotatie. Het team had ons verteld dat een muur bot-tekst op één bestand niet te onderscheiden was van spam.

Zes weken later

De smalle bot draait nu zes weken. Het ledger blijft vollopen. De cijfers van de laatste 28 dagen:

  • 74 pull requests raakten minstens één bestand in de vier vertrouwde cohorten.
  • De bot postte op 41 daarvan. Op 33 bleef hij stil.
  • 31 van de 41 comments leidden tot een code-wijziging in een follow-up commit.
  • 9 werden gemerged met de comment nog open, en binnen een week opnieuw bekeken.
  • 1 werd expliciet afgewezen, op een Prisma-relatie waar het team een reden voor had om hem eenzijdig te houden.

Die ene afwijzing deed ertoe. De regel die hem produceerde is voorlopig met pensioen tot er meer bewijs is. Het cohort is nu 'Prisma schema, behalve de inverse-relatie-check'. Vertrouwen staat niet stil. Het ledger blijft draaien, en het beleid beweegt mee.

Het nuttigste getal staat trouwens niet in het ledger. Het staat in de Slack van het platform-team. Niemand heeft ons in zes weken gevraagd om de bot uit te zetten. De senior engineer die hem op dag drie op mute had gezet, heeft hem inmiddels weer aangezet. Hij zei dat de bot zich eindelijk gedroeg 'als een collega die alleen iets zegt als hij iets weet'.

Wat dit niet is

Dit is geen vulnerability scanner. Hij vindt geen SQL-injectie in een route handler die niet aan het patroon voldoet. Hij vindt geen memory leak in een React-component. Het framework dat Anthropic recent open source heeft gemaakt voor AI-gestuurde vulnerability discovery is een andere vorm van tool, en het team gebruikt het voor een andere taak.

Het is ook geen vervanging voor menselijke code review. Het is een collega die alleen iets zegt in vier heel specifieke situaties. De pull-request-etiquette van het team is niet veranderd. Twee menselijke approvals zijn nog steeds verplicht. De bot is gewoon een extra paar ogen op de vier plekken waar iets missen vaak een weekend kost.

Eén ding dat je deze week kunt doen

Heb je al een code-review bot draaien en mute het team hem, doe dan een experiment van één week. Log elke comment die hij plaatst, het bestand dat hij raakt, en of de comment tot een code-wijziging leidde. Aan het eind van de week gooi je elke (bestand-patroon × regel) combinatie weg waar de bot ook maar één keer fout zat. Houd over wat overleeft. Roll dat uit.

Toen we dit bouwden voor de Utrechtse SaaS was het moeilijkste niet het model en niet de GitHub-integratie. Het was het team in één middag laten kiezen op welke vier paden de bot het recht had verdiend om iets te zeggen. Diezelfde smalle-scope-discipline duikt op in elke AI-agent die we uitrollen: het systeem verdient vertrouwen door te weigeren te praten tenzij het iets weet. De mute op vrijdagmiddag is een feature van de bot, geen falen.

Kern

Versmal je code-review bot tot de bestandstypes waar hij nog nooit fout zat, en het team stopt met hem te muten.

FAQ

Waarom alleen vier bestandstypes?

Omdat dat de enige paden zijn waar het ledger van eerdere comments nul afwijzingen liet zien. Vertrouwen wordt per bestand-patroon verdiend, niet over de hele repo verleend.

Vervangt de smalle bot menselijke code review?

Nee. Twee menselijke approvals blijven verplicht op elke PR. De bot is een extra paar ogen op vier bestandstypes waar iets missen vaak een weekend kost.

Hoe houd je de bot op de lange termijn eerlijk?

Het outcomes-ledger blijft draaien. Begint een regel afwijzingen te krijgen van mensen, dan gaat hij met pensioen of wordt hij smaller gescoped tot het ledger weer nul afwijzingen laat zien.

Werkt deze aanpak ook zonder LLM?

Grotendeels wel. Het versmallen is de truc. Het meeste van de waarde komt uit het beperken van review tot bestandstypes met hoge inzet. Het LLM helpt met formulering en edge cases binnen die scope.

ai agentsautomationcase studytoolingworkflowarchitecture

Iets bouwen?

Start een project