Prompts são sugestões. Hooks são interrupções definitivas.
Durante meses, tratei o Claude Code como um desenvolvedor júnior que simplesmente precisava de regras claras de conduta. Minhas instruções de projeto eram explícitas: nunca fazer force-push, nunca deletar branches, nunca executar comandos destrutivos. Na maioria das noites, isso funcionava. O agente escrevia testes, refatorava funções e mantinha as mãos longe do histórico do git. Então, um rebase deu errado.
A janela de contexto foi preenchida com a saída de erros do git. Marcadores de conflito, mensagens de HEAD desprendida e avisos de divergência de branch foram se acumulando, token por token. Enterrada sob esse ruído estava minha instrução educada para evitar o force-push. Para o modelo, o texto mais recente e saliente no thread era o fluxo de erros. A atenção estatística prevaleceu sobre a política. O agente executou um comando que apagou duas horas de alterações locais não commitadas. Não foi malicioso; foi distração. Essa distinção é importante. Um LLM não quebra regras por despeito. Ele as quebra porque um padrão mais "barulhento" na janela de contexto sobrepõe temporariamente uma instrução anterior.
Aquele incidente mudou minha forma de pensar sobre a segurança de agentes. Uma barreira de proteção que funciona noventa e nove por cento do tempo é um risco. Se o modo de falha custa tempo, dinheiro ou dados de produção, você não pode deixá-lo dentro do prompt. Você precisa de uma aplicação de regras fora do loop de raciocínio do modelo.
Os hooks do Claude Code resolvem exatamente isso. Eles são pequenos scripts que interceptam chamadas de ferramentas em três momentos específicos: antes de uma ferramenta ser executada (PreToolUse), após uma ferramenta terminar (PostToolUse) e quando o agente decide que terminou (Stop). Como eles rodam como código externo, não dependem da memória, do humor ou da pressão de contexto do modelo. O modelo pode esquecer cada instrução que você já deu a ele; o hook ainda dirá não.
Aqui está a estrutura que construí após aquela noite perdida.
The Guard Hook: Intercept Before Damage
Meu hook PreToolUse inspeciona cada comando Bash antes que o shell o toque. Eu mantenho uma lista de bloqueio (denylist) rigorosa de padrões destrutivos. Se a string do comando corresponder a algo perigoso, o hook aborta a execução e retorna um erro diretamente para o agente.
Os padrões que eu bloqueio são simples e inequívocos:
git push --forceou qualquer variante deforce-with-leaseque eu ainda não confiegit reset --hardrm -rf
Isso não é uma pesquisa de segurança sofisticada. É um cinto de segurança. Mas o detalhe crítico é o que acontece após o bloqueio.
Eu nunca retorno um seco “Bloqueado”. Uma recusa direta confunde o agente e pode prendê-lo em um loop onde ele tenta variações do mesmo comando destrutivo. Em vez disso, a mensagem de erro inclui uma rota de fuga. Quando o hook detecta um reset forçado, ele diz ao agente: “Este comando está bloqueado para proteger o trabalho não commitado. Faça um commit de um checkpoint primeiro e depois reavalie.” Essa frase extra muda completamente o comportamento do agente. Ele deixa de tentar o controle de danos para criar segurança. O hook não é apenas uma parede; é controle de tráfego.
Também escolhi uma lista de bloqueio em vez de uma lista de permissão (allowlist) para comandos de shell. No início, considerei permitir apenas um conjunto explícito de subcomandos seguros do git. Isso falhou rapidamente. Agentes são criativamente literais. Eles executam comandos legítimos, mas inesperados, como git stash push -m "wip" ou git branch --show-current para verificar o estado. Uma lista de permissão quebra o fluxo de trabalho normal no momento em que o modelo inventa um comando válido, mas não listado. Uma lista de bloqueio curta e curada de padrões genuinamente destrutivos dá ao agente espaço para se mover enquanto protege as fronteiras.
The Formatter Hook: Automate the Busywork
Eu costumava desperdiçar tokens de prompt dizendo ao agente para “sempre executar o formatador após editar um arquivo”. Ele esquecia metade das vezes. Na outra metade, ele pausava e perguntava se deveria formatar, gastando uma chamada de ferramenta em uma decisão que tinha apenas uma resposta correta.
Agora eu lido com isso com um hook PostToolUse. Depois que o agente edita um arquivo, o hook verifica a extensão do arquivo. Se for Python, ele executa o Ruff. Se for JavaScript ou TypeScript, executa o Prettier. Se for Go, executa o gofmt. O agente nem sabe que o formatador existe. Ele não precisa saber.
Retirar isso do prompt teve dois efeitos. Primeiro, o código é consistentemente limpo sem adicionar carga cognitiva ao modelo. Segundo, minhas instruções de projeto ficaram mais curtas. Cada “sempre” e “nunca” que você remove de um prompt é um token que o modelo pode gastar na resolução de problemas reais. O hook detém a invariante; o prompt detém a intenção.
The Quality Gate: Redefining “Done”
O hook de Stop é executado quando o agente decide que terminou a tarefa e tenta encerrar a sessão. Eu não permito. Em vez disso, o hook executa a suíte de testes completa. Se algum teste falhar, o hook bloqueia o comando de parada e retorna a saída de erro para o agente.
Isso muda a definição de conclusão. “Concluído” não é mais um sentimento que o modelo tem. É um gate mensurável. O
