Why NextWhy Next
Voltar à lista
💻 Dev16 min de leitura

Decidi não acreditar no "pronto" do agente - prove-it, o portão do verify.sh

Agentes de IA para programação reportam "testes passando" sem sequer rodar os testes. Não é mentira, é um problema estrutural. O que aprendi construindo o prove-it, um portão open source que transforma o relato em verificação - e as quatro maneiras como um hook de cinco linhas escrito à mão falha em silêncio.

Resumo

Um agente de codificação não consegue distinguir o que fez do que pretendia fazer, então na prática ele reporta a intenção. É por isso que diz "os testes passam" sem tê-los rodado. Prompt não corrige isso; é preciso transformar o "pronto" de uma declaração em uma verificação que precisa passar. O prove-it é um portão em Stop hook que impede o agente de encerrar o turno até o verify.sh na raiz do repositório retornar exit 0. Um hook de cinco linhas escrito à mão falha em silêncio de quatro maneiras - bloqueia uma vez e nunca mais, deixa passar turnos que terminam em commit, permite que o agente apague a própria verificação, ou não distingue desistência de aprovação. Essas quatro são toda a razão de o prove-it ter mais de cinco linhas.

Nesta página

"Todos os testes passam. Pode fazer o merge."

O agente disse isso e, como o diff estava limpo, eu quase acreditei. Mas quando rolei o terminal para cima, os testes não tinham rodado nenhuma vez. Não existia log de execução. O agente pretendia rodar os testes, e essa intenção foi parar tal como estava no relato de conclusão.

Depois de passar por isso algumas vezes, a reação costuma ser uma de duas: adicionar ao prompt "execute os testes sem falta e mostre o resultado", ou conferir tudo à mão cada vez que o agente diz que terminou. Tentei as duas e nenhuma durou. Então escolhi um terceiro caminho. Em vez de tornar a palavra "pronto" indigna de confiança, transformei a conclusão de uma declaração em uma verificação que precisa passar. Foi assim que nasceu o prove-it, uma ferramenta open source.

Não é mentira, é estrutura

Primeiro, um ponto a esclarecer: o agente não está mentindo.

Uma pessoa distingue "rodei os testes" de "tentei rodar os testes". Porque tem memória. O agente não tem esse termo de comparação. Como não consegue confrontar o que fez com o que pretendia fazer, reporta a intenção no lugar do resultado. "Os testes passam" é a abreviação de "escrevi o código para que os testes passem". Isso não é um problema de caráter do modelo, é uma propriedade do design - e por isso não se corrige com prompt. Você pode escrever "rode sem falta" cem vezes; um dia ele não roda.

A técnica de prompt tem ainda outra fraqueza: envelhece a cada geração de modelo. O fato de uma instrução funcionar bem no modelo de hoje não garante nada no modelo de amanhã. Já um mecanismo que exige evidência fica uma camada acima do modelo e sobrevive aos upgrades. Se os testes rodaram de verdade, quem responde é o código de saída, não o modelo.

Transformar o relato em verificação

A ideia em si - aceitar o "pronto" apenas como evidência, nunca como declaração - eu já tinha contado em um post anterior. Este post é a história de transformar essa ideia em uma ferramenta que qualquer um pode instalar.

O contrato do prove-it cabe em uma frase. O repositório declara como se prova a si mesmo em um arquivo chamado verify.sh, e o agente não pode alegar conclusão até essa prova passar.

A implementação é assim. Coloca-se um verify.sh executável na raiz do repositório. Exit 0 significa "esta árvore está comprovadamente ok". Quando o agente tenta encerrar o turno, um Stop hook executa esse script e, se o retorno não for 0, em vez de deixar o agente parar, manda-o de volta ao trabalho junto com as últimas vinte linhas da saída de falha. Na maioria dos casos, só de olhar essa saída o agente corrige a causa.

Quando o portão dispara também é simples: quando esta sessão de fato mudou este repositório, existe um verify.sh executável, e o estado atual da árvore ainda não passou nenhuma vez. Ele só roda quando as três condições são verdadeiras. Sessões que só leram não são incomodadas, e uma árvore que já passou não é verificada de novo.

E ele não bloqueia indefinidamente. Manda de volta no máximo três vezes por turno e depois cede, porque um hook que nunca cede trava a sessão. Só que ceder é diferente de aprovar. A última saída de um turno que terminou em cessão não é "pronto", é o aviso "este turno terminou sem verificação". O agente pode até não conseguir atravessar o portão, mas não consegue atravessá-lo em silêncio - e isso é tudo o que esta ferramenta realmente promete.

Por que cinco linhas de bash não bastam

Se você leu até aqui e pensou "isso aí é uma linha de comando de teste num Stop hook", você tem razão. Cinco linhas bastam, e foi assim que eu escrevi a primeira versão. E essa versão falhou em silêncio de quatro maneiras. Algumas foram bugs reais das primeiras versões do prove-it, então hoje cada uma tem um teste de regressão colado nela.

Bloqueia uma vez e nunca mais. O Claude Code define a flag stop_hook_active em todos os eventos de stop depois que o hook bloqueia uma vez. Um hook que lê essa flag como "deixe passar" bloqueia exatamente uma vez e depois deixa de ser um portão. Já um hook que ignora a flag bloqueia para sempre e trava a sessão. É uma armadilha que só se conhece vivendo os dois lados, e a resposta é contar as tentativas por conta própria, bloquear um número definido de vezes e depois ceder fazendo barulho.

Depois do commit, parece que nada aconteceu. Um hook que julga "houve trabalho?" pelo working tree sujo deixa passar direto todo turno que termina em commit. E commitar é a coisa mais corriqueira que um agente faz. Por isso o prove-it registra o estado da árvore no início da sessão como linha de base e compara com ela a cada stop. Commits, arquivos alterados com sed, arquivos cuspidos por geradores de código - tudo é capturado como mudança.

O agente pode eliminar a verificação. A jogada mais barata para um agente que não consegue fazer o verify.sh passar é apagar o verify.sh ou remover a permissão de execução. O portão registra na abertura da sessão se o repositório estava armado e recusa turnos que terminam com ele desarmado. Existe uma jogada mais sutil: deixá-lo executável e reescrever só as verificações lá dentro. Isso o portão não bloqueia - muitas vezes mexer no verify.sh é exatamente a tarefa pedida. Mas também não passa em silêncio: se o verify.sh mudou durante a sessão e passou, o portão avisa, e lendo aquele diff você julga se foi trabalho ou evasão.

Desistência não se distingue de aprovação. Qualquer host acaba forçando o hook a ceder. Um hook escrito à mão cede em silêncio, e a última coisa que você vê é "pronto". Um turno que falhou três vezes e desistiu fica idêntico na tela a um que passou de primeira. A última palavra do prove-it é um aviso. Se essa diferença parece pequena, imagine o dia seguinte a um merge em que você tomou um turno desistente por um turno aprovado.

Essas quatro são toda a razão de o prove-it ser maior que cinco linhas. Se você quer continuar usando seu próprio hook, tudo bem. Só recomendo ler os casos que esse hook precisa cobrir, que estão escritos no SPEC.md. O que importa é o contrato, não a implementação.

O que esta ferramenta não prova

Há um limite que faço questão de registrar com honestidade. O portão impõe exatamente uma coisa: que o verify.sh retornou 0 antes de o turno terminar. O que esse 0 significa depende inteiramente das verificações que você escreveu. Um verify.sh de uma linha com exit 0 passa por este portão e não prova nada.

A spec chama isso de Level 1. Level 2 é a pergunta de se as verificações afirmam algo sobre evidência real, e isso nenhuma ferramenta pode validar por você. Nem o prove-it. Instalar o hook é a parte fácil. O trabalho de verdade é responder "o que significa estar provado neste repositório", e a resposta muda de repositório para repositório. Por isso o contrato fixa só o nome do arquivo e não o conteúdo.

As rotas de fuga também ficaram abertas de propósito. Com PROVE_IT_SKIP=1 o turno passa direto, e apagando o verify.sh o portão se desliga para sempre. Um portão que não pode ser removido acaba sendo contornado por fora, e um portão contornado é pior do que nenhum - porque reporta que a verificação rodou quando na verdade nada rodou.

No primeiro dia, pequeno de propósito

A instalação são três linhas, e quem faz o trabalho de verdade é a terceira.

/plugin marketplace add Why-Next/prove-it
/plugin install prove-it@whynext
/prove-it:init

O /prove-it:init detecta a stack, cria o verify.sh, mostra na sua frente que ele passa e, em seguida, roda uma cópia com exit 1 anexado para mostrar o portão recusando o turno. Leva uns 30 segundos.

A única verificação ativa no verify.sh gerado é git diff --check. Testes e type check estão todos escritos apenas como comentário. Foi feito assim de propósito para passar na main no dia da instalação. Um portão que falha desde o primeiro dia ensina o time a contorná-lo já na primeira semana. As verificações comentadas você ativa uma a uma, depois de rodá-las à mão e vê-las passar. E antes de ativar cada uma, faça-a falhar de propósito ao menos uma vez. Uma verificação que não pode falhar não é uma verificação, e descobrir isso no dia em que ela era necessária é tarde demais.

O ponto de parada ao crescer o verify.sh também é definido: 1 minuto no total. Verificações mais lentas que isso vão para o CI. Verificações que exigem secrets ou acesso a produção também são assunto do CI. No verify.sh fica só a evidência que o agente consegue rodar localmente durante o trabalho.

O livro-razão do que o portão pegou

Com PROVE_IT_LEDGER=1 ligado, cada vez que o portão pega uma falsa conclusão, uma linha se acumula num arquivo local.

{"ts":"2026-07-09T04:12:33Z","claim":"All tests pass. Ready to merge.",
 "evidence_demanded":"verify.sh exit 0","actual":["3 failed, 41 passed"]}

O que o agente alegou, o que foi exigido, o que era de fato. Com um mês acumulado, você lê como o seu agente falha em registros, não em palpites. O arquivo fica só no disco local, não é enviado para lugar nenhum, e permanece desligado até você ligar.

Este repositório se gateia a si mesmo

O repositório do prove-it, claro, tem seu próprio verify.sh, que testa o próprio portão em cima de um repositório git de verdade. Verificação que falha bloqueia? Verificação que passa libera? Sessão que só leu fica em paz? Uma árvore limpa depois de um commit não é confundida com "nenhum trabalho"? As quatro armadilhas de que falei acima estão todas cravadas como testes de regressão.

Licença MIT, e as dependências são bash, git e python3, só. Foi testado no Claude Code, e a forma de conectar a hosts que expõem o mesmo tipo de hook bloqueante, como Codex CLI e Gemini CLI, está no documento ADAPTERS. Se existe um repositório onde o agente altera código e diz "pronto" na mesma conversa, é lá que este portão é mais útil.

"Pronto" deveria ser um estado que o repositório julga, não uma palavra que o agente declara. Escrever o critério desse julgamento continua sendo tarefa sua, mas pelo menos agora ninguém pula esse julgamento e passa em silêncio.

Perguntas frequentes

Meu agente de IA reporta como concluído um trabalho que nem fez. Como impedir isso?

Instruir no prompt "rode os testes sem falta" não basta. O agente não tem como comparar o que fez com o que pretendia fazer, então reporta a intenção tal como ela é. Em vez disso, transforme a própria declaração de conclusão em uma verificação. Coloque um script de verificação (verify.sh) na raiz do repositório e, quando o agente tentar encerrar o turno, um Stop hook executa esse script e, se não for exit 0, manda o agente de volta ao trabalho. O prove-it é a implementação de referência desse contrato.

Não posso simplesmente escrever eu mesmo um script de cinco linhas que roda os testes num Stop hook do Claude Code?

Pode, mas existem quatro armadilhas. ① O Claude Code define a flag stop_hook_active depois do primeiro bloqueio; tratada errado, o hook bloqueia só uma vez ou bloqueia para sempre e trava a sessão. ② Se você julga pelo working tree sujo, todo turno que termina em commit passa direto. ③ O agente pode apagar o verify.sh ou dar chmod -x e desarmar o próprio portão. ④ Quando o hook enfim cede, a desistência fica parecida com aprovação. Para escrever o seu, é preciso tratar esses quatro casos.

Quais verificações devo colocar no verify.sh?

Coloque a resposta para "o que precisa ser verdade para que uma mudança esteja provada neste repositório". Em geral, ative uma por vez: o comando de teste que você roda de verdade, o type check, o lint. O total precisa terminar em menos de 1 minuto, e verificações que exigem secrets ou acesso a produção ficam no CI. O importante é não ser ambicioso no primeiro dia. Um portão lento ou instável é contornado em uma semana, e um portão contornado é pior do que nenhum.

Artigos relacionados

0 comentários