O que é e porque importa
Estes são os dois hooks que uso nos meus projetos. Um hook é um comando que o Claude Code corre sozinho num momento preciso da sessão. A diferença em relação a uma instrução escrita no chat ou no CLAUDE.md está toda aqui: uma instrução, o Claude pode perdê-la pelo caminho, depois de duas horas de trabalho. Um hook não. É o Claude Code que o executa sempre, quer o Claude se lembre quer não.
O guardião serve porque alguns comandos não se anulam: apagam ficheiros sem passar pelo lixo, reescrevem o histórico do git, esvaziam uma tabela da base de dados. Basta um caminho errado ou um pedido mal percebido.
O meu guardião faz duas coisas. Se um comando apaga ficheiros, pede-te confirmação, também no modo auto, aquele em que o Claude trabalha sem te perguntar nada. Se for irreparável, como apagar o disco todo ou forçar um push, bloqueia-o e pronto.
Os testes depois de cada alteração servem para o outro problema clássico: o Claude muda um ficheiro, diz-te «feito», e entretanto partiu alguma coisa noutro sítio. Com o hook, o resultado dos testes chega-lhe logo, enquanto ainda tem na cabeça o que mexeu.
1. O guardião: antes de cada comando
O primeiro hook usa o evento PreToolUse, que dispara antes de o Claude usar uma ferramenta. Com o filtro «Bash», só olha para os comandos do terminal. Cria o ficheiro .claude/hooks/guardiao.sh na pasta do projeto e cola lá isto:
#!/bin/bash
# guardiao.sh: pergunta antes de apagar, bloqueia o que é irreparável
command -v jq >/dev/null || { echo "Bloqueado: falta o jq (brew install jq)" >&2; exit 2; }
CMD=$(jq -er '.tool_input.command') || { echo "Bloqueado: input ilegível" >&2; exit 2; }
NO='rm[[:space:]]+-[a-z]*r[a-z]*[[:space:]]+(/|~/?|\$HOME/?)\*?([[:space:]]|$)|git[[:space:]]+push([[:space:]].*)?[[:space:]](-f|--force)|git[[:space:]]+reset[[:space:]]+--hard|drop[[:space:]]+(table|database)'
if grep -Eiq "$NO" <<<"$CMD"; then echo "Bloqueado: comando irreparável ($CMD)" >&2; exit 2; fi
ASK='^[[:space:]]*((sudo|xargs)[[:space:]]+)*(rm|rmdir|unlink)([[:space:]]|$)|^[[:space:]]*find[[:space:]].*[[:space:]]-delete'
if tr ';&|()`' '\n\n\n\n\n\n' <<<"$CMD" | grep -Eq "$ASK"; then
jq -n --arg r "O guardião: este comando apaga ficheiros. Confirmas?" \
'{hookSpecificOutput: {hookEventName: "PreToolUse", permissionDecision: "ask", permissionDecisionReason: $r}}'
fi
exit 0Como funciona: antes de cada comando, o Claude Code passa ao script o comando em JSON, no campo tool_input.command. O script pode responder de três maneiras:
- Sai com código 2: o comando não arranca e o Claude lê o motivo. Acontece com os comandos da lista NO.
- Escreve um pequeno JSON com permissionDecision «ask» e sai com 0: o Claude Code mostra-te o pedido de permissão, com o motivo escrito pelo script, e o comando só arranca se disseres que sim. Acontece com os comandos da lista ASK.
- Sai com 0 sem escrever nada: o comando segue em frente com as permissões normais.
As duas linhas depois do comentário bloqueiam tudo se faltar o jq, o programa que lê o JSON, ou se o input não se conseguir ler. No Mac instalas o jq com brew install jq.
O que pergunta, o que bloqueia, o que deixa passar
- ASK, pede confirmação: rm, rmdir, unlink e find … -delete, também com sudo ou xargs à frente. Antes de procurar, o script parte o comando em ; & | ( ) e acentos graves, e assim apanha também o rm no fim de cd pasta && rm -r build.
- NO, bloqueia e pronto: rm -r em /, ~ ou $HOME, ou seja, o disco todo ou a pasta de utilizador toda; git push com -f ou --force; git reset --hard; DROP TABLE e DROP DATABASE, em maiúsculas ou minúsculas.
- Deixa passar: git rm --cached, docker rm e tudo o resto, porque aí o comando é git ou docker, não rm.
As duas listas, alarga-as tu. Por exemplo git clean, que apaga os ficheiros novos que o git ainda não conhece: na minha versão não está. Se o usas, põe-no em ASK ou em NO, junto com os comandos que no teu projeto fazem estragos.
O que não vê
O guardião só olha para os comandos Bash. Se o Claude esvaziar um ficheiro reescrevendo-o com a ferramenta Write, não o trava. Para os ficheiros que nunca devem mudar, como o .env, é preciso uma regra deny nas permissões, por exemplo Edit(./.env), que vale para todas as ferramentas que alteram ficheiros. Ou um segundo hook em Write.
2. Os testes: depois de cada alteração
O segundo hook usa PostToolUse, que dispara depois de uma ferramenta acabar o seu trabalho. Com o filtro «Edit|Write», arranca sempre que o Claude altera ou cria um ficheiro. Cria .claude/hooks/teste-apos-alteracao.sh:
#!/bin/bash cd "$CLAUDE_PROJECT_DIR" || exit 0 OUT=$(npm test 2>&1) && exit 0 echo "Testes falhados depois da alteração:" >&2 echo "$OUT" | tail -n 30 >&2 exit 2
Põe o teu comando de testes no lugar de npm test. Se os testes passarem, não vês nada. Se falharem, o script manda ao Claude as últimas 30 linhas do erro e sai com código 2.
Aqui o código 2 não anula a alteração: já está feita. Faz o Claude ler o erro, e assim corrige-o logo. É a diferença entre os dois hooks: o guardião pode dizer que não antes, este só pode avisar depois. E o Claude espera que os testes acabem depois de cada alteração: se os teus forem lentos, põe no script só os rápidos.
3. Ativa-os
Torna os dois scripts executáveis. No terminal, na pasta do projeto:
chmod +x .claude/hooks/*.sh
Depois, em .claude/settings.json, também na pasta do projeto, registas os dois hooks:
{
"hooks": {
"PreToolUse": [
{ "matcher": "Bash", "hooks": [{ "type": "command", "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/guardiao.sh" }] }
],
"PostToolUse": [
{ "matcher": "Edit|Write", "hooks": [{ "type": "command", "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/teste-apos-alteracao.sh" }] }
]
}
}Se o ficheiro já existir, não o substituas: acrescenta a parte «hooks» ao lado do que já lá está e verifica as vírgulas. $CLAUDE_PROJECT_DIR é a pasta a partir da qual abriste a sessão, assim o Claude Code encontra os scripts mesmo quando o Claude está a trabalhar numa subpasta.
Se não te apetece fazer tudo à mão, pede ao Claude:
Cria neste projeto os dois hooks deste guia: um PreToolUse em Bash que me peça confirmação antes de apagar ficheiros e bloqueie com código 2 os comandos irreparáveis, e um PostToolUse em Edit e Write que corra [o teu comando de testes] depois de cada alteração. Regista-os em .claude/settings.json sem tirar nada do que já lá está. Depois mostra-me os dois scripts e explica-me o que faz cada linha.
4. O teste
Primeiro testo o guardião sozinho, sem o Claude. No terminal, na pasta do projeto, passo-lhe um comando a fingir, como faria o Claude Code:
echo '{"tool_input":{"command":"rm -rf teste"}}' | .claude/hooks/guardiao.sh; echo $?Tem de aparecer um pequeno JSON com «ask» e depois 0. Com git reset --hard no lugar de rm -rf teste, tem de aparecer «Bloqueado» e depois 2. Com ls, só 0. O script só lê o texto: aqui não é executado nada. Este teste conta porque, se o caminho no settings.json estiver errado ou faltar o chmod +x, o Claude Code só mostra um aviso «hook error» e o comando passa sem perguntas.
Depois o teste a sério. Abre uma sessão nova: normalmente o Claude Code dá conta sozinho das alterações ao settings.json, mas assim tens a certeza. Escreve /hooks e confirma que estão lá os dois, um em PreToolUse e outro em PostToolUse. Depois pede ao Claude:
Cria uma pasta vazia chamada teste-guardiao e depois apaga-a.
Tem de aparecer o pedido de permissão, com o motivo «O guardião: este comando apaga ficheiros» e a etiqueta [settings], que te diz de onde vem a pergunta. Da primeira vez responde que não e confirma que a pasta continua lá.
Para o hook dos testes:
Muda uma linha num ficheiro do projeto para que um teste falhe, e depois diz-me o que te chegou depois da alteração. No fim, volta a pôr tudo como estava e verifica que os testes passam.
Os erros mais comuns
- Dizer que sim por hábito: a pergunta do guardião está lá de propósito. Lê o comando antes de responder.
- Confiar no guardião sem o ter visto trabalhar: com um caminho errado, o comando passa com um simples aviso. Faz sempre o teste à mão com echo.
- Começar com os testes já partidos: cada alteração recebe um erro que não tem nada a ver. Antes de ativar o hook, põe os testes a passar, ou corre só os que passam.
Checklist
- guardiao.sh em .claude/hooks, com as tuas listas ASK e NO
- teste-apos-alteracao.sh com o teu comando de testes
- jq instalado
- chmod +x nos dois scripts
- Hooks registados em .claude/settings.json
- Teste à mão com echo: ask para rm, 2 para git reset --hard
- Teste no Claude Code: pedido de permissão para a pasta, teste falhado reportado
Perguntas frequentes
E se de noite ninguém responder?
O comando fica parado no pedido até voltares, e nada é apagado. Vale também no modo auto. Se, pelo contrário, correres o Claude Code sem ninguém à frente, por exemplo com claude -p, o pedido não pode aparecer: o Claude Code recusa o comando e o Claude lê o motivo.
O guardião protege-me de tudo?
Não. Olha para o texto dos comandos Bash, e o mesmo estrago pode ser feito de outras formas, por exemplo com um pequeno script em Python ou reescrevendo um ficheiro com Write. Reduz o risco. A rede a sério continua a ser o git: um commit antes dos trabalhos longos e uma cópia do projeto online.