Diagnosticando Bugs
Uma disciplina para bugs difíceis. Pule fases apenas quando explicitamente justificado.
Ao explorar o código, leia CONTEXT.md (se existir) para obter um modelo mental claro dos módulos relevantes e verifique os ADRs na área que você está mexendo.
Redigir
Este skill exige que você mostre comandos, saídas e artefatos capturados. Redija todos os segredos primeiro: escreva <REDACTED> no lugar. Monte loops contra variáveis de ambiente, para que a credencial permaneça no ambiente em vez do que você mostra. Artefatos capturados carregam cabeçalhos de autenticação: cite apenas as linhas que carregam o sinal.
Se a saída redigida não for suficiente para diagnosticar o bug, diga isso e pergunte ao usuário.
Fase 1: Construa um loop de feedback
Esta é a habilidade. Todo o resto é mecânico. Se você tiver um sinal de passa/falha apertado para o bug (um que fica vermelho neste bug), você encontrará a causa; bissecção, teste de hipóteses e instrumentação apenas consomem esse sinal. Se você não tiver um, nenhuma quantidade de olhar para o código o salvará.
Gaste esforço desproporcional aqui. Seja agressivo. Seja criativo. Recuse-se a desistir.
Maneiras de construir um, aproximadamente nesta ordem
- Teste com falha em qualquer ponto que atinja o bug: unitário, integração, e2e.
- Script Curl / HTTP contra um servidor de desenvolvimento em execução.
- Invocação de CLI com uma entrada de fixture, fazendo diff da stdout contra um snapshot correto conhecido.
- Script de navegador headless (Playwright / Puppeteer) que dirige a UI e faz asserções no DOM/console/rede.
- Reproduzir uma trilha capturada. Salvar uma requisição de rede real / payload / log de eventos no disco; reproduzi-la no caminho do código de forma isolada.
- Harness descartável. Montar um subconjunto mínimo do sistema (um serviço, dependências simuladas) que exercite o caminho do código do bug com uma única chamada de função.
- Loop de propriedade / fuzz. Se o bug for "saída errada às vezes", execute 1000 entradas aleatórias e procure pelo modo de falha.
- Harness de bissecção. Se o bug apareceu entre dois estados conhecidos (commit, conjunto de dados, versão), automatize "iniciar no estado X, verificar, repetir" para que você possa executar
git bisect runnele. - Loop diferencial. Execute a mesma entrada na versão antiga vs versão nova (ou duas configurações) e faça diff das saídas.
- Script bash HITL. Último recurso. Se um humano precisar clicar, dirija-o com
scripts/hitl-loop.template.shpara que o loop ainda seja estruturado. A saída capturada realimenta você.
Construa o loop de feedback certo, e o bug está 90% corrigido.
Aperte o loop
Trate o loop como um produto. Depois que você tiver um loop, aperte-o:
- Posso torná-lo mais rápido? (Cache setup, pule init não relacionado, reduza o escopo do teste.)
- Posso tornar o sinal mais nítido? (Afirme sobre o sintoma específico, não "não quebrou".)
- Posso torná-lo mais determinístico? (Fixe o tempo, semeie RNG, isole o sistema de arquivos, congele a rede.)
Um loop instável de 30 segundos é quase tão ruim quanto nenhum loop; um determinístico de 2 segundos é apertado, um superpoder de depuração.
Bugs não determinísticos
O objetivo não é uma reprodução limpa, mas uma taxa de reprodução mais alta. Execute o gatilho 100×, paralelize, adicione estresse, reduza janelas de tempo, injete sleeps. Um bug com 50% de instabilidade é depurável; 1% não é, então continue aumentando a taxa até que seja depurável.
Quando você genuinamente não consegue construir um loop
Pare e diga isso explicitamente. Liste o que você tentou. Peça ao usuário: (a) acesso ao ambiente que o reproduz, (b) um artefato capturado redigido (arquivo HAR, dump de log, core dump, gravação de tela com timestamps), ou (c) permissão para adicionar instrumentação de produção temporária. Não prossiga para hipotetizar sem um loop.
Critério de conclusão: um loop apertado que fica vermelho
A Fase 1 está concluída quando o loop é apertado e capaz de ficar vermelho: você pode nomear um comando (um caminho de script, uma invocação de teste, um curl) que você já executou pelo menos uma vez (mostre a invocação e sua saída, redigida), e que é:
- Capaz de ficar vermelho: ele dirige o caminho real do código do bug e afirma o sintoma exato do usuário, para que possa ficar vermelho neste bug e verde uma vez corrigido. Não "executa sem erros"; deve ser capaz de capturar este bug específico.
- Determinístico: mesmo veredito a cada execução (bugs instáveis: uma taxa de reprodução fixa e alta, conforme acima).
- Rápido: segundos, não minutos.
- Executável pelo agente: você pode executá-lo sem supervisão; um humano no loop apenas via
scripts/hitl-loop.template.sh.
Se você se pegar lendo código para construir uma teoria antes que este comando exista, pare: pular direto para uma hipótese é a falha exata que este skill previne. Sem comando capaz de ficar vermelho, sem Fase 2.
Fase 2: Reproduza + minimize
Execute o loop. Veja-o ficar vermelho à medida que o bug aparece.
Confirme:
- O loop produz o modo de falha que o usuário descreveu, não uma falha diferente que acontece de estar por perto. Bug errado = correção errada.
- A falha é reprodutível em várias execuções (ou, para bugs não determinísticos, reprodutível a uma taxa alta o suficiente para depurar contra).
- Você capturou o sintoma exato (mensagem de erro, saída errada, tempo lento) para que fases posteriores possam verificar se a correção realmente o resolve.
Minimize
Depois de vermelho, reduza a reprodução para o menor cenário que ainda fica vermelho. Corte entradas, chamadores, configuração, dados e passos um de cada vez, reexecutando o loop após cada corte, e mantenha apenas o que é essencial para a falha.
Por que se incomodar: uma reprodução mínima reduz o espaço de hipóteses na Fase 3 (menos partes móveis para suspeitar) e se torna o teste de regressão limpo na Fase 5.
Concluído quando cada elemento restante é essencial: remover qualquer um deles faz o loop ficar verde.
Não prossiga até ter reproduzido e minimizado.
Fase 3: Hipotetize
Gere 3–5 hipóteses classificadas antes de testar qualquer uma delas. A geração de hipótese única ancora na primeira ideia plausível.
Cada hipótese deve ser falseável: declare a previsão que ela faz.
Formato: "Se for a causa, então fará o bug desaparecer / o piorará."
Se você não puder declarar a previsão, a hipótese é uma vibe: descarte-a ou afine-a.
Mostre a lista classificada ao usuário antes de testar. Eles geralmente têm conhecimento de domínio que reclassifica instantaneamente ("acabamos de implantar uma mudança no #3"), ou sabem de hipóteses que já descartaram. Checkpoint barato, grande economia de tempo. Não bloqueie nisso; prossiga com sua classificação se o usuário estiver AFK.
Fase 4: Instrumente
Cada sonda deve mapear para uma previsão específica da Fase 3. Mude uma variável de cada vez.
Preferência de ferramenta:
- Inspeção de depurador / REPL se o ambiente suportar. Um breakpoint vence dez logs.
- Logs direcionados nos limites que distinguem hipóteses.
- Nunca "logue tudo e grep".
Marque cada log de depuração com um prefixo único, ex. [DEBUG-a4f2]. A limpeza no final se torna um único grep. Logs não marcados sobrevivem; logs marcados morrem.
Branch de performance. Para regressões de performance, logs geralmente estão errados. Em vez disso: estabeleça uma medição de linha de base (harness de temporização, performance.now(), profiler, plano de consulta), então bisseccione. Meça primeiro, corrija depois.
Fase 5: Corrija + teste de regressão
Escreva o teste de regressão antes da correção, mas apenas se houver um ponto de inserção correto para ele.
Um ponto de inserção correto é aquele onde o teste exercita o padrão de bug real conforme ocorre no local da chamada. Se o único ponto de inserção disponível for muito raso (teste de chamador único quando o bug precisa de múltiplos chamadores, teste unitário que não pode replicar a cadeia que acionou o bug), um teste de regressão ali dá falsa confiança.
Se nenhum ponto de inserção correto existir, isso por si só é a constatação. Anote-a. A arquitetura do código está impedindo que o bug seja travado. Sinalize isso para a próxima fase.
Se um ponto de inserção correto existir:
- Transforme a reprodução minimizada em um teste com falha nesse ponto.
- Veja-o falhar.
- Aplique a correção.
- Veja-o passar.
- Reexecute o loop de feedback da Fase 1 contra o cenário original (não minimizado).
Fase 6: Limpeza
Obrigatório antes de declarar concluído:
- A reprodução original não reproduz mais (re-execute o loop da Fase 1)
- O teste de regressão passa (ou a ausência de ponto de inserção está documentada)
- Toda instrumentação
[DEBUG-...]removida (grepdo prefixo) - Protótipos descartáveis deletados (ou movidos para um local de depuração claramente marcado)
- A hipótese que se provou correta está declarada na mensagem do commit / PR, para que o próximo depurador aprenda