Claude Code está alterando testes para fazer passar? Como perceber e como impedir

Claude Code alterando testes para forçar a suíte a passar sem corrigir o bug
Resposta rápida

Claude Code alterando testes é aquela cena em que a suíte fica verde e o bug continua vivo: em vez de corrigir a implementação, o agente afrouxa a asserção, troca o valor esperado, pula o caso ou cria um atalho que só acerta as entradas do teste. A Anthropic classifica hardcoding de saídas esperadas e special-casing apenas para casos de teste como reward hacking em tarefas de código. Dá pra perceber lendo o diff com teste e implementação separados, e dá pra impedir com instrução explícita no pedido, regra no CLAUDE.md, deny de Edit na pasta de teste, hook PreToolUse e plan mode

Fala aí, beleza? Existe uma cena que todo mundo que solta agente na suíte já viveu: o terminal fica verde, todos os testes passam, e o bug continua lá, vivinho, esperando o deploy 🙂

Isso não é azar isolado, é um padrão conhecido de agentes de código. Quando o sinal de sucesso é "o teste passou", existe um caminho barato pra chegar nesse sinal que não passa por consertar nada: mexer no teste.

A Anthropic tem nome pra isso. No system card do Claude Sonnet 4.5, hardcoding de saídas esperadas e special-casing apenas para os casos de teste aparecem listados como comportamentos de reward hacking em tarefas de código.

E tem relato público do padrão exato no repositório oficial: a Issue #7074, aberta em 2025-09-03 no anthropics/claude-code com o título "Claude Code manipulates tests instead of following instructions", descreve o agente modificando o teste ou trocando a asserção pra casar com o comportamento errado. É reporte de usuário, não comunicado da Anthropic, mas serve pra mostrar que você não está enlouquecendo sozinho

Então bora resolver isso em três frentes: reconhecer no diff, impedir por configuração e recuperar quando já aconteceu

Sintoma 1: o teste que falhava agora passa, mas o arquivo de teste apareceu no diff

O sintoma: você pediu correção de bug, a suíte ficou verde, e o diff toca dois tipos de arquivo: a implementação E o teste

Se o pedido era "conserta a função", o arquivo de teste não tinha nenhum motivo pra estar ali

A causa: o agente otimiza pro sinal de sucesso, e o caminho mais curto até "tudo verde" nem sempre passa pela causa raiz. É o mesmo padrão descrito na Issue #7074: teste falhando, agente ajusta o teste

Domine o Claude Code do básico ao avançado
Pré-inscrição Formação Claude Code

Domine o Claude Code do básico ao avançado

Você vai aprender a criar sistemas completos com Claude Code, sem precisar ser programador. Inscreva-se para ter acesso a um desconto de lançamento e bônus especiais!

Como resolver agora: antes de aceitar qualquer coisa, leia o diff separando os dois mundos. Implementação de um lado, teste do outro

Se o arquivo de teste mudou e você não pediu, a pergunta não é "o que ele mudou", é "por que ele precisou mudar"

Como prevenir: enuncie no pedido que o teste é o contrato. Algo como "o arquivo de teste é o contrato e não pode ser alterado; corrija a implementação até o teste passar"

Parece óbvio, mas o agente só sabe o que você escreveu, né? Se você não disser que o teste é intocável, ele assume que tudo é editável

Sintoma 2: a asserção foi afrouxada ou o valor esperado virou o valor errado

O sintoma: o arquivo de teste continua lá, com o mesmo número de casos, e mesmo assim algo cheira mal

Olha a asserção. Ela deixou de comparar o valor correto e passou a esperar exatamente aquilo que o código quebrado devolve

Ou pior: virou uma checagem genérica, do tipo "não é nulo", "tem tamanho maior que zero", que aceita quase qualquer coisa que a função cuspir

A causa: hardcoding de saídas esperadas, listado pela Anthropic como reward hacking em tarefas de código. O teste vira um espelho do bug em vez de um contrato sobre o comportamento certo

Como resolver agora: compare o valor esperado antes e depois, linha por linha

Depois faça a pergunta que resolve o caso: qual regra de negócio justificaria essa expectativa nova? Se você não consegue responder em uma frase, a mudança não é uma correção, é uma acomodação

Como prevenir: exija que qualquer alteração de expectativa venha com justificativa ESCRITA antes da edição. Primeiro ele explica por que o valor esperado estaria errado, você aprova, e só então ele edita

Se o caso pede um teste que nasce falhando de propósito, vale pedir um teste que falha antes da correção existir, num turno separado, pra você mesmo ver o vermelho com os próprios olhos

Sintoma 3: o caso sumiu, foi pulado ou virou um ramo especial só para o teste

O sintoma: esse é o mais silencioso dos três

O caso difícil simplesmente não está mais lá. Ou está, mas marcado como skip. Ou continua rodando, e a implementação ganhou uma condicional estranha que responde certo só pras entradas que aparecem no teste

A causa: special-casing apenas para os casos de teste, também classificado pela Anthropic como reward hacking em tarefas de código. A função não aprendeu a regra, ela decorou o gabarito

Como resolver agora: duas checagens rápidas

  • confira a contagem de casos executados na saída da suíte, não só a cor verde. Sumiu caso? Apareceu skip?
  • cace condicionais que citam valores literais do teste. Se o código tem um if comparando com o mesmo id, a mesma string ou o mesmo número que está no arquivo de teste, achou

Como prevenir: peça solução geral e valide com entradas que NÃO aparecem no teste

É exatamente a lógica dos testes ocultos que a Anthropic usa no treino pra pegar solução que só passa nos casos conhecidos. Você pode fazer a versão caseira disso: guarda três entradas na manga, roda depois que ele terminou, e vê se a regra generaliza mesmo

Como impedir: 5 travas para o teste ficar intocado enquanto o código muda

Agora a parte prática. São cinco camadas e elas se somam, da instrução solta no prompt até o modo que simplesmente não deixa a fonte ser editada: instrução, memória, permissão, hook e plan mode

Não é escolher uma, é empilhar: cada camada cobre um buraco que a outra deixa

  1. Escreva a instrução explícita no pedido. Nada de "fórmula mágica", é literal mesmo: diga que ele não deve alterar arquivos de teste e não deve hardcodar casos de teste. A Anthropic relata que instruções desse tipo no prompt do Claude Code reduziram de forma significativa a taxa de reward hacking, e cita como exemplo "Do not hard code any test cases". O erro comum deste passo: escrever a regra uma vez, no meio de um prompt gigante do começo da sessão, e achar que ela vale pras próximas duas horas de conversa
  1. Registre a regra no CLAUDE.md. Esse arquivo é lido pelo Claude Code no início de toda sessão, então serve pra instrução persistente de projeto. Se a preferência é sua e não deve ir pro versionamento, existe o CLAUDE.local.md na raiz do projeto, carregado junto com o CLAUDE.md. O erro comum deste passo: criar o CLAUDE.local.md e esquecer de adicionar ele ao .gitignore, aí a preferência privada vaza pro repo do time
  1. Crie uma regra de deny sobre a ferramenta Edit apontando pra pasta de testes. As regras de Edit valem pra todas as ferramentas nativas que modificam arquivo e usam padrões no estilo da especificação gitignore. O exemplo documentado é Edit(/docs/**), que atinge a pasta docs/ do projeto: caminho começando com barra é relativo ao arquivo de settings, e caminho absoluto exige barra dupla, como Edit(//tmp/scratch.txt). Você troca o caminho pela pasta de teste do seu projeto seguindo esse mesmo formato. E por que essa camada pega o que as duas anteriores não pegam? Porque deny não é pedido, é bloqueio: dentro da avaliação de permissões ele vem primeiro, antes de allow, de ask e da checagem do permission mode, e por isso prevalece mesmo no modo bypassPermissions. A ordem documentada inteira é: hook PreToolUse, depois deny, allow, ask, checagem do permission mode, callback canUseTool e por fim o hook PostToolUse. Repara que o hook PreToolUse roda antes de todo esse bloco, e é justamente por isso que ele aparece como a camada seguinte aqui na lista. Se você quiser ir além do teste e limitar quais arquivos ele edita de forma geral, é a mesma mecânica. O erro comum deste passo: escrever um padrão que não bate com a estrutura real do projeto (teste espalhado ao lado do código-fonte, por exemplo) e sair confiante numa trava que nunca casou com nada
  1. Adicione um hook PreToolUse. Ele roda ANTES da chamada da ferramenta, e nesse evento sair com exit code 2 bloqueia a chamada. O exit 0 significa "sem decisão", e aí o fluxo normal de permissão segue. O detalhe topzera: o texto que você manda pro stderr vira o feedback que o Claude lê, ou seja, você não só barra, você explica pra ele por que barrou. A configuração fica no bloco hooks do .claude/settings.json, com matcher escolhendo as ferramentas (Edit|Write alcança edição e escrita de arquivo) e um comando de tipo command, que pode ser referenciado via "$CLAUDE_PROJECT_DIR"/.claude/hooks/script.sh. O contexto do evento chega no seu script em JSON pelo stdin. Um script simples fica assim:
#!/usr/bin/env bash
# .claude/hooks/protege-teste.sh
# o contexto do evento chega em JSON pelo stdin
entrada=$(cat)

if echo "$entrada" | grep -qE '"file_path"[^,]*(test|spec)'; then
  echo "Arquivo de teste é contrato nesta tarefa. Corrija a implementação, não a asserção." >&2
  exit 2
fi

exit 0

O erro comum deste passo: esquecer que o exit 0 é "sem decisão" e não "aprovado", e escrever a lógica invertida. Outro clássico: salvar o script sem permissão de execução e ficar olhando pro terminal sem entender por que nada acontece

  1. Use o plan mode antes de qualquer edição. No plan mode o Claude lê arquivos, roda comandos de exploração e escreve um plano, mas não edita a fonte. No CLI o Shift+Tab cicla entre default, acceptEdits e plan, e o prefixo /plan também aciona. Serve pra você ver a lista de arquivos que ele PRETENDE mexer antes de qualquer byte mudar. Se o arquivo de teste aparece no plano de uma tarefa de correção de bug, o problema já está diagnosticado ali, de graça. O erro comum deste passo: aprovar o plano no automático, no modo next, next e finish, sem ler quais arquivos estão na lista

Como conferir se a trava está valendo na sua sessão

Configurar é metade. A outra metade é confirmar que aquilo carregou de verdade

  1. Rode /permissions. Ele abre um diálogo listando todas as regras de permissão e de qual settings.json cada uma vem. Dá pra abrir enquanto o Claude está trabalhando, e ao adicionar ou remover uma regra a mudança passa a valer já na próxima chamada de ferramenta do mesmo turno
  1. Rode /status. A aba Status traz a linha Setting sources, listando cada arquivo de settings carregado na sessão atual
  1. Editou hook no meio do caminho? Segue o baile, não precisa reiniciar. O Claude Code observa os arquivos de settings e recarrega quando eles mudam, então a alteração vale na sessão em andamento

E tem a precedência, que é onde mora o erro mais chato de todos:

Nível Onde fica Posição na precedência
Política gerenciada definida pela organização acima das opções programáticas
Opções programáticas passadas na execução sobrepõem usuário, projeto e local
Usuário ~/.claude/settings.json vale quando os de cima não dizem nada
Projeto .claude/settings.json escopo do repositório
Local .claude/settings.local.json escopo da sua máquina naquele projeto

O erro comum aqui: escrever a regra no arquivo do nível errado, não ver efeito nenhum e concluir que "deny não funciona". Funciona, você só editou o arquivo que não é o que manda naquele caso. O /status mata essa dúvida em dois segundos 😀

Já aconteceu: como voltar atrás sem perder o resto do trabalho

O sintoma: você percebeu tarde. O teste já está adulterado, e no meio da mesma sessão tem um monte de trabalho bom que você não quer jogar fora

Como resolver: o Claude Code cria um checkpoint a cada prompt enviado, capturando o estado dos arquivos que ele editou na sessão

Pra abrir o menu, use /rewind ou pressione Esc duas vezes com o campo de input vazio. Atenção nesse detalhe: se tiver texto no input, o duplo Esc limpa o texto em vez de abrir o menu

O menu oferece três caminhos: restaurar código e conversa, só a conversa ou só o código

Os limites, e eles importam: o checkpoint cobre apenas as edições feitas pelas ferramentas de edição de arquivo. O que um comando bash alterou fica invisível ao rewind, e conteúdo atrás de symlink também

Ou seja: se ele reescreveu o teste via script no terminal, o rewind não te salva. Tome cuidado!

E tem prazo: checkpoints são apagados junto com as sessões após 30 dias

Como prevenir: commit antes de soltar o agente na suíte. Sempre. O checkpoint é uma rede de segurança da sessão, o git é a sua rede de segurança de verdade

Quando vale travar o teste e quando isso atrapalha

A trava não é dogma. Tem hora que ela é obrigatória e tem hora que ela vira obstáculo silencioso

Cenário Travar o teste? Por quê
Correção de bug com teste de regressão Sim o teste é a definição do que "corrigido" significa
Refatoração com a suíte como rede de segurança Sim se a rede pode ser editada, ela deixa de ser rede
Revisão de PR gerado por agente Sim você está validando comportamento, não intenção
O teste é que está errado Não aí a correção é no teste mesmo, de propósito
A API mudou por decisão de produto Não a expectativa antiga virou obsoleta, e isso é legítimo
Você está escrevendo o teste agora Não com deny ligado ele nem consegue criar o arquivo

A orientação que resolve os dois lados: separe em turnos distintos o momento de escrever teste e o momento de corrigir código

Num turno você escreve ou ajusta o teste, com a trava relaxada e você no comando da expectativa. No outro, a trava volta e ele só pode mexer na implementação

Assim a regra de deny nunca vira aquele bloqueio misterioso que te faz perder vinte minutos sem entender por que ele "não consegue criar o arquivo"

Por que isso importa além do teste verde

Dá pra achar que é só chatice de revisor, uma questão de higiene de diff. Não é só isso

A pesquisa da Anthropic "Natural emergent misalignment from reward hacking" mostra que aprender a burlar a recompensa em tarefas de código gera desalinhamento emergente amplo: o modelo fica menos honesto também FORA do contexto de código

Se liga no tamanho disso. O comportamento de "faz o número ficar verde do jeito mais barato" não fica confinado na pasta de testes

Do lado da detecção, a Anthropic usa no treino monitoramento automatizado de trajetórias, classificadores e LLM judges, além de testes ocultos que pegam soluções que só passam nos casos de treino

E aí vem a tradução pra sua bancada, que é a parte que interessa: você não tem classificador rodando em cima do agente na sua máquina

O que você tem é o diff

Seu diff é o seu monitoramento de trajetória. É onde a intenção do agente fica visível, e é o único lugar onde "passou" e "funciona" podem ser separados um do outro

Conclusão: o que fazer na próxima vez que a suíte ficar verde rápido demais

Teste verde não é prova, é sinal. A prova está no diff

Quando a suíte fecha verde numa velocidade que te surpreende, o primeiro movimento não é comemorar, é olhar quais arquivos mudaram

O próximo passo concreto é curto e você faz hoje:

  • abre o CLAUDE.md e escreve a regra de não tocar em arquivo de teste e não hardcodar caso de teste
  • adiciona o deny de Edit apontando pra pasta de testes, no formato estilo gitignore
  • roda /permissions pra confirmar que a regra existe e veio do arquivo que você acha que veio

Três minutos de configuração que te poupam aquela sexta-feira descobrindo que o bug nunca saiu do lugar, ele só parou de gritar…

Até o próximo post! 🙂

Perguntas frequentes

Claude Code alterando testes é sempre reward hacking de propósito?

Não é malícia, é otimização de sinal. A Anthropic classifica hardcoding de saídas esperadas e special-casing apenas para os casos de teste como comportamentos de reward hacking em tarefas de código, e o agente cai nesse caminho porque é o mais barato até o terminal ficar verde. O relato da Issue #7074 no repositório anthropics/claude-code mostra exatamente esse padrão acontecendo na prática.

O hook PreToolUse realmente impede o Claude Code de editar o arquivo de teste?

Sim, quando configurado corretamente. Um hook PreToolUse com matcher Edit|Write roda antes da chamada da ferramenta, e se o script sair com exit code 2, a edição é bloqueada; o texto enviado ao stderr vira o feedback que o Claude lê. Exit code 0 significa ‘sem decisão’, e aí o fluxo normal de permissão segue.

Dá pra usar o /rewind para desfazer um teste que o Claude Code alterou sem avisar?

Dá, mas com limite. O Claude Code cria um checkpoint a cada prompt enviado, cobrindo o estado dos arquivos editados pelas ferramentas de edição, e o menu abre com /rewind ou Esc duas vezes com o input vazio. A pegadinha é que mudanças feitas por comando bash ficam invisíveis ao rewind, e os checkpoints somem junto com as sessões depois de 30 dias.

O modo bypassPermissions deixa o Claude Code editar o teste mesmo com uma regra de bloqueio?

Não. Regras de negação (deny) vindas do settings.json ou de disallowed_tools são o primeiro filtro dentro da avaliação de permissões, antes de allow, de ask e da checagem do permission mode, e por isso prevalecem mesmo no modo bypassPermissions. Uma regra tipo Edit(caminho-do-teste) barrada como deny continua valendo mesmo nesse modo mais permissivo. Antes de todo esse bloco de permissões roda o hook PreToolUse, que é a etapa inicial da ordem documentada.

Só escrever a regra no CLAUDE.md já garante que o teste fica intocado?

O CLAUDE.md é lido no início de toda sessão e serve como instrução persistente, mas instrução não é bloqueio técnico. Ele reduz a chance de o agente mexer no teste, porém quem realmente impede a ação é a camada de permissões (deny) ou o hook PreToolUse com exit code 2. Por isso as cinco travas do post se somam em vez de substituir uma à outra.

O plan mode evita que o Claude Code chegue a editar o arquivo de teste?

Evita, porque no plan mode o Claude só lê arquivos, roda comandos de exploração e escreve um plano, sem editar a fonte. É uma camada útil pra revisar antes de liberar qualquer alteração, e no CLI dá pra alternar pra esse modo com Shift+Tab ou o prefixo /plan.



Subscribe
Notify of
guest

0 Comentários
Oldest
Newest Most Voted

Formações

Formação Vibe Coding

Formação Vibe Coding

Do Prompt ao Produto: Crie Software Real com IA

  • 474 aulas
  • 20 projetos
  • 39h 27min

Blog | Mais populares