Stop Hook no Claude Code: verificar antes de “pronto”
Adicione uma checagem local que bloqueia o encerramento quando um teste falha ou um arquivo não existe.
Conteúdo
Stop Hook no Claude Code: verificar antes de “pronto”
Quando o Claude Code diz “pronto”, isso só significa que o agente quer encerrar o turno. Não prova que os testes rodaram nem que o artefato de build está atualizado. Um Stop Hook executa uma checagem local curta nesse momento e pode impedir o encerramento quando uma condição objetiva falha. Ele não substitui CI, uma suíte completa de testes ou a revisão humana.
Stop Hook, CLAUDE.md e CI têm papéis diferentes
- Um Stop Hook executa uma verificação rápida no fim da resposta:
git diff --check, um teste específico ou a existência de um arquivo esperado. - CLAUDE.md informa ao agente quais regras e comandos seguir, mas o arquivo não executa comandos.
- A CI roda de modo independente após um push ou pull request. Ela continua sendo a barreira obrigatória para a equipe.
Não coloque deploy, publicação ou escrita em sistemas externos nesse hook. Como ele é disparado a cada final de turno, esses efeitos colaterais são difíceis de repetir e reverter.
Defina um resultado que possa ser verificado
Antes de configurar o hook, escreva um contrato de quatro itens:
- Afirmação: o que o agente pode declarar ao fim do turno, por exemplo, “o build foi gerado”.
- Prova: o comando ou arquivo que confirma isso, como
npm test -- --runInBandetest -s dist/app.js. - Sucesso: as duas verificações terminam com código
0. - Bloqueio de Stop: em caso de falha, o hook devolve JSON com
decision: "block"e umreasoncurto.
Execute esses comandos manualmente antes de criar o hook. Se levarem vários minutos ou dependerem de rede, reduza-os a uma verificação local focada; o ciclo completo fica na CI.
Se o Claude Code usa um provedor de API, abra primeiro a documentação atual do BetterToken, configure sua própria API Key na ferramenta e envie uma solicitação curta de teste. Depois confirme no Dashboard o modelo, o status e o uso de tokens esperados. O hook local não precisa da chave; nunca a grave nos logs.
Configure um Stop Hook mínimo
Adicione um hook de projeto em .claude/settings.json com timeout curto:
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "./scripts/check-before-stop.sh",
"timeout": 30
}
]
}
]
}
}
Salve a checagem como scripts/check-before-stop.sh. Os comandos deste exemplo são de um projeto Node; substitua-os por comandos e caminhos que existam no seu repositório.
#!/usr/bin/env sh
set -eu
if [ -t 0 ]; then
input='{"stop_hook_active":false}'
else
input=$(cat)
fi
stop_hook_active=$(printf '%s' "$input" | node -e '
let raw = "";
process.stdin.on("data", chunk => raw += chunk);
process.stdin.on("end", () => {
try { process.stdout.write(String(Boolean(JSON.parse(raw).stop_hook_active))); }
catch { process.stdout.write("false"); }
});')
block_stop() {
if [ "$stop_hook_active" = "true" ]; then
printf '%s\n' "A checagem Stop continua falhando: $block_reason. Execute-a manualmente; a CI continua obrigatória." >&2
exit 0
fi
BLOCK_REASON="$block_reason" node -e 'process.stdout.write(JSON.stringify({decision:"block",reason:`A checagem Stop falhou: ${process.env.BLOCK_REASON}`}) + "\n")'
exit 0
}
if ! npm test -- --runInBand >/dev/null 2>&1; then
block_reason='execute npm test e corrija o teste que falhou'
block_stop
fi
if ! test -s dist/app.js; then
block_reason='gere dist/app.js novamente'
block_stop
fi
printf '%s\n' 'Checagem Stop aprovada: testes e dist/app.js'
exit 0
Torne o arquivo executável:
chmod +x scripts/check-before-stop.sh
Segundo a documentação atual do Claude Code, um Stop Hook pode devolver JSON estruturado com código 0: decision: "block" impede o fim do turno e reason informa a causa. Códigos diferentes de zero e timeouts têm semântica própria de erro de hook; não os use como único contrato de bloqueio. A checagem deve caber no prazo configurado.
Na tentativa seguinte de encerrar, stop_hook_active será true se o Claude Code já estiver continuando por causa de um Stop Hook. No exemplo, a ramificação fail-open escreve um aviso curto em stderr e retorna 0, em vez de bloquear cegamente a mesma falha. Isso evita um loop sem enfraquecer a barreira da CI. Se precisar bloquear de novo, implemente um contador e um limite próprios, sem presumir uma quantidade fixa não documentada de tentativas.
Teste o ciclo completo manualmente
Não basta confirmar que o script inicia no terminal; teste todos os caminhos:
- No script, troque temporariamente
dist/app.jspordist/missing.jse executeprintf '%s\n' '{"stop_hook_active":false}' | ./scripts/check-before-stop.sh; echo $?. O esperado é JSON com"decision":"block", umreasoncurto e código0. - Restaure o caminho correto, gere o build e repita o comando. O resultado esperado é o código
0. - Indique novamente um arquivo ausente, peça ao Claude Code uma mudança pequena e reversível e confirme que
decision: "block"mantém a conversa aberta com a causa da checagem. - Sem corrigir o caminho, execute
printf '%s\n' '{"stop_hook_active":true}' | ./scripts/check-before-stop.sh; echo $?. O script deve escrever um aviso curto em stderr e devolver0: assim você valida a ramificação que evita o loop. - Restaure o caminho ou gere um artefato atual. No próximo encerramento, o hook deve retornar
0e liberar o fim do turno.
Esse teste diferencia um Stop Hook funcional de um script que falha no terminal, mas ainda permite que o Claude Code pare.
Recupere-se de um Stop bloqueado
Separe dois casos. Se o hook devolver JSON com decision: "block", leia reason: a condição verificada falhou normalmente. Execute essa verificação, corrija o teste ou o código, confirme que o arquivo veio do comando atual e repita a tarefa do Claude Code.
Se o comando do hook terminar com código diferente de zero ou timeout, isso é um erro de execução do hook, não um bloqueio confirmado por reason. Leia o erro e stderr, execute o script manualmente e corrija caminho, permissões, dependência ou limite de tempo antes de testar outra vez.
Registre somente o nome da verificação e seu resultado. Não imprima API Keys, conteúdo de .env, prompts completos nem logs integrais de teste. Um git diff --check aprovado não valida a lógica de negócio; a presença de um arquivo não prova que o build seja recente. O hook só consegue validar as afirmações que você codificou explicitamente.
Mantenha o workflow de API separado
Com um provedor de API, crie e gerencie a chave na sua própria conta. O Stop Hook continua local: ele não precisa acessar a chave, prompts completos ou logs do Dashboard. Se houver um incidente de API, consulte a documentação atual para Base URL e configuração; não misture esse diagnóstico com a checagem local de encerramento.
Fontes
- Claude Code Hooks Reference — consultada em 22 de agosto de 2026
- BetterToken: configurar o Claude Code