AI

Seis semanas de revisão de código por IA: o que ela pegou e o que deixou passar

A cartoon miner shines a headlamp on a code panel, pinning bugs in the light while one bug escapes in the shadow
On this page

O comentário começou com três palavras: Isso quebra a prod.

Era um PR de cerca de quinhentas linhas – em um repo que eu tinha acabado de encontrar, e não fui eu quem o escreveu. Um agente escreveu, após abrir três arquivos que o diff não mostrava.

Semanas depois, o mesmo agente deixou um bug passar para staging, e ele ficou lá por doze dias. Entre essas duas histórias está o que uma IA pode fazer em um code review, e o que ela não pode.

Nas minhas primeiras semanas nesse projeto, cerca de 25 PRs eram criados semanalmente, em dois repos que eu nunca tinha aberto.

Eu abria um diff, lia, entendia cada linha, e ainda assim não conseguia dizer se estava certo. Porque "certo" dependia de coisas que não estavam no diff: as convenções que a equipe seguia, a tela que aquele componente desenhava, o outro endpoint que chamava a mesma rota.

Aprovar assim é dar o aval em algo que você não checou. E um CI verde torna isso confortável demais. Seu nome fica no PR, e o bug que ninguém viu vai para produção com a sua aprovação ao lado.

O review que eu queria fazer era o ideal. Você faz o checkout da branch do autor, executa o que deve ser executado e confirma que nada quebrou. Você checa se o novo teste testa alguma coisa, ou se ele passaria sem a correção. Você o estressa: faz login com a role que não deveria ver aquela tela, diminui a janela até a tabela transbordar, envia o formulário vazio. E você checa se o PR se encaixa na casa, se usa o helper e o padrão que o módulo vizinho já adotou, ou se reinventa tudo.

Fazer isso para 25 PRs por semana, em código que eu não conhecia, não batia a conta.

E grande parte desse código tinha sido escrita por um agente. Se um agente escreveu, poderia outro fazer o review para o qual eu não tinha tempo? E se ele não pudesse fazer tudo, qual parte eu poderia delegar?

Um número para ter em mente antes de responder: pergunte às pessoas por que elas revisam código e a resposta principal é encontrar bugs. Bugs são apenas 14% do que as revisões realmente produzem.

O que procuramos em uma revisão

Esse número vem de Alberto Bacchelli e Christian Bird, que estudaram a revisão de código dentro da Microsoft em 2013. Eles observaram 17 desenvolvedores revisando, entrevistaram cada um deles e pesquisaram 873 programadores e 165 gestores.

Encontrar bugs é a principal motivação para 44% dos programadores e 44% dos gestores. Mas quando os autores classificaram 570 comentários de revisão, defeitos ficaram em quarto lugar, com 14%.

Ambas as listas do estudoUma das perguntas foi: por que você faz revisão de código? Cada pessoa escolheu seus três principais motivos, e os autores os classificaram por pontuação: 1. Encontrar defeitos 2. Melhoria de código 3. Soluções alternativas 4. Transferência de conhecimento 5. Conscientização da equipe 6. Melhoria do processo de desenvolvimento 7. Evitar quebras de build 8. Propriedade compartilhada do código 9. Rastreamento de justificativas 10. Avaliação da equipe E as nove categorias dos 570 comentários: | Categoria | Comentários | |—|—| | Melhoria de código | 29% | | Compreensão (perguntas e dúvidas) | ~22% | | Comunicação social | ~15% | | Defeitos | 14% | | Impacto externo | ~5% | | Testes | ~4% | | Ferramenta de revisão | ~3% | | Transferência de conhecimento | 2% | | Diversos | ~6% | > NOTA: os valores marcados ~ foram lidos do gráfico do artigo. Os exatos (29%, 14% e os 12 comentários de transferência de conhecimento) estão no texto. A maior fatia, 29%, é melhoria de código, que é exatamente estilo e convenção: use a prática que a equipe já usa, remova código morto, torne-o legível. Em segundo lugar vem a compreensão, pessoas perguntando o que algo faz e por quê. Os autores ouviram a mesma coisa nas entrevistas, sem perguntar:

a coisa mais difícil ao fazer um code review é entender o motivo da mudança

Para entender, os revisores leem a descrição, tentam executar o código alterado, enviam e-mail ao autor e, de 20% a 40% das vezes, levantam-se e vão conversar pessoalmente. Os donos dos arquivos alterados nem leem a descrição: eles vão direto para o que conhecem. Todos os outros ficam travados. "Não conhecer arquivos (ou [lidar com] arquivos novos) é um motivo principal para não entender uma mudança."

Eu não conhecia um único arquivo.

Uma década depois, Turzo e Bosu classificaram manualmente 2.500 comentários do OpenStack Nova e pediram a 160 desenvolvedores que avaliassem a utilidade de cada tipo. O que os desenvolvedores mais valorizam é o que menos aparece: defeitos funcionais, validação e lógica lideram as avaliações e somam apenas 19% dos comentários. Documentação e organização de código representam mais de 40%.

Tabela de Turzo e Bosu| Category | Usefulness rating (1-5) | % of comments | |—|—|—| | Functional defect | 4.38 | 0.48% | | Logical | 4.11 | 2.28% | | Question | 3.99 | 13.56% | | Documentation | 3.73 | 33.32% | | Organization of code | 3.68 | 7.68% | ## Um revisor que não precisa ser humano

Comecei com o óbvio: usar IA para reunir o contexto que eu não tinha. Ela lê o ticket, o CLAUDE.md, os ADRs e o código ao redor antes de olhar o diff. A primeira versão foi uma skill que invoquei no Claude Code, no terminal, um PR por vez. Pouco depois, tornou-se cc-harness: um pequeno programa em torno do Claude Code que pega o número de um PR, escolhe a skill certa para o repo, executa todo o review por conta própria e me entrega o resultado em um arquivo, para que eu o leia antes de postar qualquer coisa.

Ao longo do caminho, eu fui e realmente estudei code review, e isso se tornou um estudo com mais de vinte notas de leitura. A frase que escrevi logo no início resume onde cheguei:

O code review não se tornou obsoleto. Ele se tornou mais necessário. Mas o que procuramos nele mudou.

E uma decisão surgiu da leitura: desisti da ideia de que um agente que revisa precisa se comportar como um humano. A pergunta passou a ser "o que um revisor máquina deve fazer que uma pessoa não faria?".

Algumas coisas são baratas apenas para uma máquina. Abrir a docstring de três arquivos de distância para ver se ela ainda é verdadeira. Encontrar cada chamador de uma rota. Iniciar o app para cada PR, sem ficar com preguiça, às quatro da tarde de uma sexta-feira.

O checklist do Collina

A habilidade de revisão não começou do zero. Matteo Collina publica as habilidades que ele usa para trabalhar no Node.js. Uma delas, nodejs-core, tem uma regra chamada reviewing-prs.md: o guia que ele segue para revisar PRs do core. Mensagem de commit, escopo, testes, documentação, CI. E uma seção de red flags que eu gosto muito:

Testes que passariam sem a correção: o teste não exercita realmente o caminho do código alterado. Verifique revertendo mentalmente (ou realmente) a correção e checando se o teste ainda passaria.

Peguei esse esqueleto e o adaptei ao projeto. Os blocos do Collina ainda estão lá, com quase os mesmos nomes: Scope and size, Commit messages, Quality red flags, e a régua para quando solicitar mudanças e quando aprovar com comentários. Por cima vieram as regras da casa: autorização como seu próprio eixo, padrões de backend e frontend, variáveis de ambiente.

Mas o checklist do Collina delega a execução ao CI. Ele pergunta se o CI passou e mostra como ler o resultado. Executar coisas localmente é algo que ele diz para você sugerir ao contribuidor. Para o Node core, isso faz total sentido: o CI é enorme e não há tela para olhar.

Neste projeto, a história é diferente. Em um PR, a tabela de membros começou a transbordar a tela. Em 1280×800, o container tinha 902px e a tabela tinha 1443px, e as colunas de E-mail e Editar ficavam atrás de uma rolagem horizontal. Nenhum teste mede a largura, então o CI não tinha como detectar isso, e um diff não mostra pixels.

É como revisar a planta de uma cozinha e nunca abrir uma gaveta. A planta pode estar certa e a gaveta ainda pode bater na geladeira.

A revisão começa a executar as coisas

Então a revisão começou a executar as coisas por conta própria. Para cada PR, o agente:

  • cria um git worktree isolado no commit do PR, fora da minha árvore de trabalho;
  • executa typecheck, lint e testes, com MySQL em um container descartável;
  • serve o próprio frontend do PR na porta 3100 (nunca 3000, que serve a minha branch);
  • abre o app no Chrome através do Chrome DevTools MCP, faz login, vai para o fluxo alterado e o testa.

Screenshots são apenas evidências. O que confirma um problema de layout é medir, com evaluate_script, no DOM real. Aquela tabela foi pega assim, em 1280 e novamente em 1440:

Inline GitHub review comment anchored to lines 160 to 165 of registered-users-table.tsx. It says this is a real bug: putting the role chips inline inside the fullName cell blows the column past its size; at 1280x800 the members-page container is 902px and table.scrollWidth is 1443px, so 541px of the row, including Email and the Edit action, sits behind a horizontal scroll; at 1440x900 the shortfall is still 381px.

Quando o backend não consegue iniciar, o frontend roda nos handlers do MSW que o projeto já possui. O agente ativa todos eles, popula uma organização de teste e marca cada ajuste com // REVIEW SCRATCH: Not part of the PR. para que ninguém confunda o comportamento do mock com o comportamento do PR.

NOTA: O MSW também esconde coisas. Em um PR, a tela mostrava Make e Model corretamente porque o mock servia esses campos. O backend nunca os enviou. O bug só apareceu ao ler o transformer. Rodar o app não substitui a leitura do código – isso a complementa.

"Isso quebra a prod."

Aquele comentário da abertura não tinha tela nenhuma.

No app legado, um PR de cerca de quinhentas linhas ativou a paginação por cursor para uma rota de transações. O autor tinha feito a lição de casa: ele verificou os outros oito chamadores da função que alterou e concluiu que a mudança era puramente aditiva.

A IA foi atrás dos chamadores da rota, não da função. Ela encontrou três telas que ainda paginavam por offset, e todas elas começariam a retornar as mesmas linhas em cada requisição. O comentário começava com três palavras:

Comentário de revisão do GitHub inline em src/server/api/transactions/get.js, linhas 87 a 89. Diz que a alteração quebra a produção: with_cursor true é definido incondicionalmente, então o find.js remove a cláusula OFFSET e a rota para de retornar total. Três outros chamadores de /api/transactions/get ainda paginam por offset, linkados como PodTransactions.jsx:232, demo/index.jsx:284 e transaction_details/pod.jsx:215. Abaixo, um bloco de sugestão substitui with_cursor true por with_cursor: before_id !== undefined e with_total: before_id === undefined.

O autor respondeu uma hora e meia antes do merge: "isso foi uma quebra real de prod", junto com o commit que a corrigiu. Nunca chegou à produção porque algo abriu três arquivos que o diff não mostrou, e que ninguém tinha motivo para abrir.

Como o harness funciona

cc-harness quase não tem código de agente. No post sobre o harness via SDK, eu mostrei o preset claude-agent-sdk que ativa as ferramentas, o system prompt e o loop do Claude Code em duas linhas. O AGENTS.md do projeto fixa isso como uma regra: "O ponto deste projeto é que o SDK já é o harness."

// trimmed from src/index.ts
const result = query({
  prompt,
  options: {
    tools: { type: "preset", preset: "claude_code" },
    systemPrompt: { type: "preset", preset: "claude_code" },
    settingSources: ["user", "project", "local"],
    skills: [reviewSkill, "humanizer"],
    mcpServers, // read from the harness's own .mcp.json
    permissionMode: "bypassPermissions",
  },
});

settingSources traz o meu ambiente: as mesmas skills e plugins que uso no claude interativo, com symlinks para ~/.claude/skills. Edite um SKILL.md e isso se aplica à próxima revisão e à próxima sessão do terminal. humanizer sempre acompanha, para cortar enchimentos. No começo, eu queria que a revisão parecesse escrita por uma pessoa; depois, vi que não havia necessidade alguma. É uma revisão de IA, e o que importa é que ela faça o seu trabalho.

O que o harness adiciona fica em torno disso, em um wrapper bash chamado cc-review. Ele detecta o repo a partir do git remote, nunca pelo nome da pasta, e escolhe a skill:

Skill routing diagram: cc-review leads to a 'which repo?' box that splits into three lanes, portal, legacy and other. In two columns, local mode and post mode: portal uses the portal's local skill and the portal's post skill; legacy uses the legacy local skill and github-pr-review; other uses local-pr-review and github-pr-review. A banner at the bottom says humanizer rides along in all of them.

Essa divisão veio de um erro. Tentei fazer com que uma única skill servisse a ambos os repositórios, e ao apontar para o app legado, ela exigiu políticas de autorização e uma camada de MSW que aquele repositório não possui. O comentário deixado no script é, para mim, a frase mais importante do projeto:

A errada não apenas deixa passar coisas, ela afirma coisas que o repositório não tem.

Dentro da skill, a revisão sempre segue os mesmos dez passos, registrados na própria lista de tarefas do agente. Essa lista é o que o cc-status lê para relatar em qual fase a revisão está:

Diagrama de uma revisão cc-harness: dez caixas em sequência, Target, Spec, Big picture, What changed, Scope check, Find problems, Verify, Write, Humanize e Lint. Um colchete sobre as primeiras cinco diz contexto antes do diff. Sob Verify, um ícone de navegador observa que cada achado é testado. Uma seta curva vai de Lint de volta para Write, observando a correção e nova execução. Após Lint, uma seta leva ao documento de notas do PR. Acima, um robô rotulado cc-status observa uma barra de progresso para a fase atual.

Duas linhas da skill resumem tudo. No Big picture: "Um diff pode estar localmente correto e ainda assim estar errado para o sistema." No Verify, onde cada achado é testado antes de se tornar um comentário: "Um falso positivo eliminado é o sistema funcionando."

A saída é um arquivo de notas com um layout fixo. No modo local, que é o que mais uso, nada vai para o GitHub: eu leio, corto o que discordo e só então posto. Um comentário, como ele fica no arquivo:

<!-- anchor path="apps/frontend/src/users/components/member-roles-panel.tsx" start_line=88 line=90 side=RIGHT -->

This is a real bug.

When `holdsNoneHere` is true the field reads `No role in this group` and
nothing else, so the `< 2` guard hides the toggle. [...]

```suggestion
          {everyGroup.length < (holdsNoneHere ? 1 : 2) ? null : (</code></pre>
<p><em>Verified: read the branches in `member-roles-panel.tsx` for a member with one
grant on another branch [...] `everyGroup.length` is 1, and the button renders nothing.</em></p>
<pre><code>
A âncora carrega o arquivo e as linhas que a API do GitHub precisa, e um linter verifica cada uma contra o diff antes que qualquer coisa seja postada. A linha de prova, <code>*Verified:*</code> quando o agente a testou e <code>*Inferred:*</code>

quando ela apenas lê, nunca vai para o PR. Ela existe para mim, quando decido se um achado permanece. ## Doze dias

Em outro PR, a IA pediu uma alteração: a documentação dizia que a desconexão era <code>event_code=2</code>, e o código usava <code>1</code>.

O autor discordou e alterou a documentação para <code>1</code>. Na re-revisão, a IA leu novamente, viu que o código e a documentação concordavam e ficou satisfeita. O PR foi aceito.

Doze dias depois, o ambiente de staging mostrou que a desconexão é <code>2</code>. Durante aqueles doze dias, o campo <code>last_disconnected</code> mostrava a última *conexão*. Quem quer que olhasse via a informação errada, com toda a confiança de um campo bem nomeado. Nunca chegou à produção, mas ficou lá o tempo todo.

Você pode contar isso como uma falha da IA. Eu conto como comportamento esperado.

Na primeira revisão, código e documentação discordavam, e isso é algo que ela pode verificar. Na segunda, eles concordavam. Qual número o dispositivo realmente envia não está no diff. Está na documentação do fornecedor, no próprio dispositivo, na cabeça de alguém que conhece o sistema. Um revisor suspeito teria perguntado por que a documentação mudou no mesmo PR que o código. O agente não pergunta, porque de dentro do PR não restava mais nada para verificar.

**A IA verifica. Um humano atesta o que está certo.** Essa parte eu não delego.

## Duas passagens

É por isso que o workflow do projeto tem duas passagens:

1. <code>cc-harness</code> faz a primeira e sempre posta a revisão como <code>COMMENT</code>. A IA não aprova e não bloqueia.
2. Um humano faz a segunda e decide: <code>APPROVE</code> ou <code>REQUEST_CHANGES</code>.

[Heander, Söderberg e Rydenfält](https://doi.org/10.1007/s10664-025-10791-2) sentaram-se ao lado de dez desenvolvedores durante 34 revisões reais. Quase metade do que passa pela cabeça de um revisor é tentar entender o contexto e a justificativa da mudança, e as ferramentas ignoram essa parte: os revisores vão procurá-la no Jira, no chat, na documentação. Os primeiros cinco passos do pipeline são essa busca, feita antes de eu chegar lá.

Eu extraí os PRs do projeto de 14 de agosto até hoje. A IA revisou 79. Das 30 descobertas que ela marcou como críticas ou altas, 28 foram confirmadas. De todas as suas descobertas, 54 eram bugs de correção, 23 eram testes ausentes ou testes que não testavam nada, e 45 eram convenções de código.

> **NOTA:** estes são poucos dados: seis semanas, uma equipe, dois repositórios, e eu mesmo classifiquei a severidade com a ajuda de um agente. E isso não mostra que a IA encontra mais do que um humano, ou o oposto, porque as duas passagens quase nunca caíram no mesmo PR.

Uma revisão com o app rodando leva cerca de 12 minutos e custa uma mediana de US$ 7,57. Isso é caro para um PR de uma linha e barato perto de uma paginação quebrada em produção.

## Build your own

<code>cc-harness</code> e as skills do projeto são privadas. Mas eu escrevi uma versão genérica da skill para este post, [<code>review-that-runs</code>](https://github.com/Codeminer42/skills/tree/main/review-that-runs), com o esqueleto do Collina e as regras que mais compensaram. Ela não sabe nada sobre o meu projeto e aprende o seu conforme avança:

```bash
npx skills add Codeminer42/skills --skill review-that-runs

Ela precisa de duas coisas no seu Claude Code. A primeira é um gh autenticado. Como ela apenas lê o PR e nunca posta, você pode executá-la com um fine-grained token de apenas leitura, com escopo nos repositórios que você revisa, com Contents, Pull requests e Metadata definidos como Read-only. gh

usa qualquer token que esteja em GH_TOKEN: “`bash
GH_TOKEN=githubpat… claude


Com esse token, quando você postar a revisão, você a posta com seu `gh` habitual. A skill nunca teve permissão para isso.

O segundo é o [Chrome DevTools MCP](https://github.com/ChromeDevTools/chrome-devtools-mcp):

```bash
claude mcp add chrome-devtools -- npx chrome-devtools-mcp@latest

Depois, peça por review PR 123 dentro do repo. Ele cria a worktree, executa os gates, inicia o app se o PR tocar em uma tela e escreve pr-123-review-notes.md sem postar nada. As regras que considero não negociáveis ficam no topo:

  • validação significa o app real, rodando no Chrome, não ler o diff;
  • cada descoberta termina com *Verified:* ou *Inferred:*, e sem prova não há comentário;
  • qualquer ajuste de mock feito para rodar o app é marcado como // REVIEW SCRATCH e nomeado na visão geral;
  • a revisão sai como COMMENT, e aprovar é uma decisão humana.

And when you post, the review closes with a single line: First pass by review-that-runs. Approving is a human’s call. Quem abre o PR sabe de onde veio e sabe que a aprovação final ainda pertence a alguém.

Se quiser ir além, a regra original de reviewing-prs.md está no repositório de skills do Collina, e o preset claude_code do Agent SDK, que mostrei no post anterior, executa a mesma skill fora do terminal.

Sua equipe notará no primeiro PR onde a revisão chega com a largura da tabela medida em pixels, ou com as três chamadas que o diff não mostrou.

Checking and attesting

Eu ainda recebo cerca de 25 PRs por semana. A diferença é que, quando abro um, o contexto já está na mesa: a tela foi aberta, os testes foram questionados, as chamadas foram encontradas e cada descoberta vem com sua prova.

O que resta para mim é a parte que não cabe no diff. Sabendo que o dispositivo envia 2.

Cada peça do harness existe para que a minha aprovação exija menos atenção. Mas ainda é uma aprovação. Quando eu aprovo, eu sei o que estou assinando.

Obrigado por ler!

Se você quiser se aprofundar:

We want to work with you. Check out our Services page!

We want to work with you

The engineers who write here are the same ones who join client teams. Let’s build something that grows with your business.

Book a call