← Voltar ao Blog

autoFixable: transformei 30 min de bate-e-volta em revisão de código em 47 segundos

Sobre os números deste texto. O padrão autoFixable vem do capítulo 12 do meu livro sobre code review com harness. Os cenários, as porcentagens e os tempos aqui são um exemplo trabalhado para mostrar o mecanismo, e não medição de um time em produção. Meça no seu repositório antes de adotar qualquer número daqui.

Cena conhecida. O comentário chega às 15h47 de uma terça-feira. “nitpick: import não usado no arquivo user-service.ts”. O autor do PR está em reunião. Volta às 16h12, vê o comentário, tira o import, sobe novo commit. O CI roda de novo por 8 minutos. O revisor só nota às 16h34, aprova e faz o merge.

Da abertura do comentário ao merge: 47 minutos. O trabalho de fato: apagar uma linha.

Eu já sabia que isso era ridículo faz uns dois anos. Todo mundo sabe. Mas a gente sempre trata como um problema de disciplina — “revisor, seja mais rápido”, “autor, verifique antes de abrir PR” — quando na verdade é um problema de classificação. Existem coisas em código review que não deveriam ter comentário nenhum. Elas deveriam ter um commit.

O capítulo 12 do meu livro sobre code review chama isso de autoFixable. Com o padrão aplicado, o mesmo tipo de correção sai da faixa de dezenas de minutos e passa para a casa dos segundos. Só que aí aparece um problema novo, e essa parte também está aqui.

O que é autoFixable, na prática

A ideia é simples e chata: separar as coisas que uma máquina pode arrumar sem consultar ninguém, das coisas que precisam de julgamento humano. Em vez de comentar “tira esse import”, você deixa o pipeline aplicar eslint --fix e commitar.

Classificação autoFixable vs non-autoFixable

A tabela de classificação, resumida:

autoFixable (a máquina resolve)non-autoFixable (humano decide)
Formatação (Prettier / Biome)Desenho de arquitetura
Lint auto-corrigível (eslint --fix)Correção de N+1
Ordenação de importsNome de variável ou função
Remoção de imports não usadosBug de lógica
Cast redundante que TypeScript infereRegra de negócio

O critério é meio brutal: se existe uma resposta única e mecânica, não é um comentário, é um commit. Se existe mais de uma resposta razoável, aí sim precisa de humano.

O workflow que eu subi (e que quase quebrou)

Meu primeiro workflow foi ingênuo. Um GitHub Action que rodava a cada PR aberto, aplicava Prettier + ESLint —fix, e commitava direto no branch do PR.

# .github/workflows/auto-fix.yml
name: Auto Fix
on:
  pull_request:
    types: [opened, synchronize]

jobs:
  autofix:
    runs-on: ubuntu-latest
    permissions:
      contents: write
    steps:
      - uses: actions/checkout@v4
        with:
          ref: ${{ github.head_ref }}
          token: ${{ secrets.GITHUB_TOKEN }}

      - uses: actions/setup-node@v4
        with:
          node-version: '22'
          cache: 'npm'

      - run: npm ci

      - name: Format
        run: npx biome check --write .

      - name: Commit if changed
        run: |
          git config user.name "github-actions[bot]"
          git config user.email "github-actions[bot]@users.noreply.github.com"
          git diff --quiet || (
            git add -A &&
            git commit -m "chore: auto-fix format and lint issues"
          )

O modo de falha aparece no primeiro PR de refatoração grande: o Action commita centenas de linhas de reformatação por cima, a diff do autor vira uma sopa, e no rebase ele conflita com o próprio commit. Com razão, ninguém acha graça.

A lição foi que autoFixable precisa acontecer ANTES do PR, não depois. Movi o mesmo lint para um pre-commit hook local + uma checagem gate no CI. O CI não fixa mais, ele só bloqueia o merge se algo autoFixable ainda passar. Isso muda todo o equilíbrio: o autor conserta na hora localmente, o revisor nunca vê o problema, o histórico do PR fica limpo.

Se você já leu documentação sobre Claude Code hooks, esse é exatamente o padrão que a Anthropic recomenda para PostToolUse — deixar biome check --write rodar depois de cada Edit do agente. Junto com um pre-commit hook local, dá para nunca gerar um commit sujo pra começar.

O que mudou nos números do time

Peguei 40 PRs antes de subir o padrão e 40 depois. Contei só os comentários puramente autoFixable (formatação, import não usado, ordenação, e afins). Ignorei bugs, decisões de nome, etc.

MétricaAntesDepois
Comentários autoFixable por PR (mediana)40
Tempo de fluxo autoFixable (mediana)32 min47 seg
Rodadas de CI por PR (mediana)32
PRs bloqueados > 24h por nitpick6 / 400 / 40

O número que importa não é o tempo, é a fila. PRs parados por mais de 24 horas por causa de nitpick travam uma fatia do backlog esperando um humano tirar um import { useState } que ninguém usava. Multiplique por uma dúzia de devs e o trimestre vai embora em bikeshedding.

Fluxo autoFixable vs fluxo tradicional de revisão

O problema novo que apareceu

Aqui vem a parte que ninguém conta.

Quando você tira todo o nitpick da revisão, sobra só o que exige julgamento. E aí acontece uma coisa engraçada: os revisores começam a se sentir inseguros. O relato típico do revisor experiente é mais ou menos assim: “eu acho que estou revisando pior. Antes eu sempre tinha algo pra apontar. Agora eu leio o PR, não vejo nada, e me sinto mal aprovando”.

O que ele estava sentindo era o fim do teatro de revisão. Metade dos comentários de “nitpick” que a gente escreve não é sobre o código. É sobre provar que a gente leu. Quando o autoFixable tira 4 dos 5 comentários possíveis, sobra 1 comentário real por PR, ou zero. E aí “aprovar sem comentar” começa a parecer displicência, mesmo quando é a resposta correta.

Resolvi com dois combinados no time:

  1. Ficou combinado escrever “revisei, sem observações” em vez de só clicar em Approve. Textualmente. Faz diferença cultural.
  2. Nas 1:1s da retrospectiva, o “comentário-métrica” foi aposentado. Ninguém mais é medido por quantidade de comentários por PR. Só por defeitos que apareceram em produção depois de aprovado.

O segundo item foi mais difícil que o primeiro. Empresas gostam de contar coisas. Contar comentários de revisão é fácil. Contar julgamento é difícil.

CodeRabbit e o batch apply do GitHub

Se você usa CodeRabbit, tem um plus interessante. O CodeRabbit gera as sugestões como blocos aplicáveis nativos do GitHub, então o revisor (ou o autor) clica em “Apply suggestion” e vira commit sem tocar em nada.

Em março de 2026 o GitHub liberou o batch apply de Code Quality suggestions, e em abril fez o mesmo para alertas de code scanning. Isso mudou a economia do padrão: em vez de aplicar 15 sugestões uma por uma, você seleciona todas e vira um único commit. É o mesmo padrão autoFixable, só que com o humano puxando o gatilho em vez do CI.

Eu ainda prefiro o pre-commit hook, porque prevenir é mais barato que remediar. Mas para times que já operam com CodeRabbit no fluxo, batch apply é uma boa evolução incremental — você não precisa mexer no pipeline de CI, só ativa e pronto.

A linha que eu não cruzo

Vale falar o contrário também. Não é que “tudo que dá pra automatizar, deve ser automatizado”. Duas coisas eu deixei explicitamente fora do autoFixable, mesmo que ferramentas modernas consigam fazer:

Rename automático. Ferramentas como TypeScript LSP fazem rename com segurança em cima do grafo de símbolos. Mas rename é uma decisão. Se um campo se chama user_id e o linter sugere userId, isso pode estar certo tecnicamente e errado semanticamente — se aquele campo veio de uma API externa que devolve snake_case, o rename cria um bug sutil. Deixei rename como sugestão manual, nunca no CI.

Remover código morto. Analisadores estáticos (dead-code-elimination do Rollup, ts-prune, etc.) apontam código não referenciado. Mas “não referenciado no grafo de imports” não é o mesmo que “não usado”. Um export pode ser consumido por um pacote externo, por reflexão, ou por um build tool específico. Deixei código morto na categoria non-autoFixable também.

O critério na dúvida: se o pior caso do fix errado é um bug em produção, não é autoFixable. Fim.

Resumo do que sobrou

  • Formatação, lint, ordenação de imports, remoção de imports não usados: pre-commit hook local + gate no CI. Zero comentário de revisão sobre isso.
  • Decisões de nome, arquitetura, N+1, regra de negócio: revisor humano, como antes.
  • Rename e remoção de código morto: manual, sob julgamento.

O impacto no time foi menos “economizamos tempo” e mais “paramos de discutir coisas que ninguém queria discutir”. O número que mais importa não é o 47 segundos. É o fato de que ninguém no time mais reclama de code review sendo lento por causa de bobagem. Sobrou só a discussão que valia a pena.

E, honestamente, o problema secundário — revisor se sentindo inseguro quando não tem o que comentar — vale meses de debate cultural. Se você adotar o padrão, prepare a retro seguinte pra falar sobre isso. Não tira nem coloca linha de código, mas ajuda o time a não recuar por reflexo.


ken imoto · WebRTC & Voice AI engineer · kenimoto.dev · TabNews

Revisão de Código com Harness Engineering Livro relacionado Revisão de Código com Harness Engineering Revisão de código em três camadas | hooks + IA + humano · AGENTS.md · CodeRabbit · GitHub Actions Ver a página do livro →