Correr comandos teus automaticamente com os hooks do Claude Code
Os hooks são comandos de shell que o Claude Code corre em momentos fixos, como antes de um comando ou depois de uma edição. Regista o que o Claude corre, bloqueia o que nunca deve correr, verifica cada ficheiro que edita e recebe um aviso quando está à tua espera.
Verificado na documentação oficial a
Nesta página9 secções
- 01O que é um hook
- 02Onde ficam os hooks
- 03Exemplo 1: registar todos os comandos
- 04Exemplo 2: bloquear force pushes
- 05Exemplo 3: verificar a sintaxe do PHP depois de cada edição
- 06Exemplo 4: saber quando o Claude está à tua espera
- 07Os hooks correm com as tuas permissões
- 08Quando um hook não funciona
- 09Fontes
Podes escrever no teu CLAUDE.md "verifica sempre a sintaxe do PHP depois de editares um ficheiro", e o Claude costuma fazê-lo. Costuma. Um hook dá a certeza. É um comando teu que o Claude Code corre sempre que chega um dado momento, decida o Claude o que decidir.
Este guia explica como funcionam os hooks e depois constrói quatro, passo a passo: um registo de todos os comandos que o Claude corre, um bloqueio aos force pushes, uma verificação de sintaxe depois de cada edição em PHP e um aviso quando o Claude está à tua espera. Os quatro foram testados em sessões reais.
O que é um hook
Um hook tem três partes:
- Um evento: o momento em que corre. Por exemplo
PreToolUse(antes de o Claude usar uma ferramenta),PostToolUse(depois de uma ferramenta correr bem) ouNotification(quando o Claude Code precisa da tua atenção). - Um filtro (
matcher): os casos a que se aplica. Nos eventos de ferramentas, é o nome da ferramenta, comoBashouEdit|Write. - Um comando: o que corre. Normalmente um comando de shell ou um script.
Quando o evento acontece, o Claude Code passa os pormenores ao teu comando em JSON, pela entrada padrão. Quando a ferramenta é o Bash, isso inclui o comando que o Claude quer correr:
{
"session_id": "abc123",
"cwd": "/home/tu/projeto",
"hook_event_name": "PreToolUse",
"tool_name": "Bash",
"tool_input": {
"command": "npm test"
}
}O teu comando lê o que precisa (os exemplos aqui usam o jq), faz o seu trabalho e responde com o código de saída:
- Saída 0: nada a opor. O Claude Code continua como de costume. No
PreToolUseisto não aprova nada: as regras de permissão normais continuam a aplicar-se. - Saída 2: um erro que bloqueia. O que escreves na saída de erros volta como motivo. Um hook
PreToolUsebloqueia a chamada à ferramenta, e o Claude lê a tua mensagem. Um hookPostToolUsenão consegue desfazer nada, porque a ferramenta já correu, mas o Claude lê a mensagem na mesma e pode corrigir o problema. - Qualquer outro código: um erro que não bloqueia. A ação avança e aparece um pequeno aviso
hook error.
Os eventos que vais usar mais:
PreToolUse: antes de uma chamada a uma ferramenta. O único destes que consegue parar uma chamada a uma ferramenta antes de ela correr.PostToolUse: depois de uma chamada a uma ferramenta correr bem.Notification: quando o Claude Code está à espera da tua permissão ou de uma resposta tua.SessionStart: quando uma sessão começa ou é retomada. O texto simples que o comando escrever é acrescentado ao contexto do Claude.UserPromptSubmit: quando envias um pedido, antes de o Claude o ver.Stop: quando o Claude acaba a resposta.
A lista completa, com mais de 30 eventos, está na referência dos hooks, nas fontes no fim.
Onde ficam os hooks
Os hooks vão para os mesmos ficheiros de definições que as regras de permissão, dentro de uma chave hooks:
~/.claude/settings.json: todos os projetos da tua máquina..claude/settings.jsonnum projeto: partilhado com toda a gente, se o puseres no git..claude/settings.local.jsonnum projeto: só tu.
Escreve /hooks numa sessão para veres todos os hooks em vigor, agrupados por evento, e o ficheiro de onde vem cada um. O menu só os mostra. Para acrescentar ou mudar um hook, edita o ficheiro de definições, ou pede ao Claude que o faça. Normalmente o Claude Code dá pela alteração sozinho, sem reiniciar.
Cada exemplo abaixo é um ficheiro de definições completo. Se o teu já tiver uma chave hooks, junta o novo evento aos que lá estão, em vez de substituíres o bloco todo.
Exemplo 1: registar todos os comandos
Este hook acrescenta a um ficheiro do projeto cada comando de shell que o Claude corre:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "jq -r '.tool_input.command' >> \"$CLAUDE_PROJECT_DIR\"/.claude/command-log.txt"
}
]
}
]
}
}O Claude Code define $CLAUDE_PROJECT_DIR como a pasta do projeto, por isso o registo vai sempre parar ao mesmo sítio, esteja o Claude a trabalhar onde estiver.
Nota
O PostToolUse só corre depois de uma chamada correr bem. No nosso teste, um comando bloqueado por outro hook nunca chegou ao registo. Para registar também as chamadas que falham, acrescenta o mesmo hook em PostToolUseFailure.
Exemplo 2: bloquear force pushes
Um hook PreToolUse corre antes da verificação de permissões, por isso consegue parar um comando mesmo em modos que o deixariam correr.
Escreve o script
.claude/hooks/block-force-push.sh#!/bin/bash # Block force pushes. Claude sees the message and can choose another way. command=$(jq -r '.tool_input.command') if [[ "$command" =~ git[[:space:]]+push.*(--force|[[:space:]]-f([[:space:]]|$)) ]]; then echo "Blocked by hook: force push is not allowed in this project. Push normally or ask the user." >&2 exit 2 fi exit 0Torna-o executável
Terminal · o teu utilizadorchmod +x .claude/hooks/block-force-push.shTesta-o à mão
Dá-lhe o mesmo tipo de JSON que o Claude Code lhe mandaria e confirma o código de saída:
Terminal · o teu utilizadorecho '{"tool_input":{"command":"git push --force origin main"}}' | .claude/hooks/block-force-push.sh; echo "exit=$?"Resultado
Blocked by hook: force push is not allowed in this project. Push normally or ask the user. exit=2Um
git push origin mainnormal deve mostrar sóexit=0.Regista-o
.claude/settings.json{ "hooks": { "PreToolUse": [ { "matcher": "Bash", "hooks": [ { "type": "command", "if": "Bash(git push *)", "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/block-force-push.sh" } ] } ] } }
O campo if usa os mesmos padrões que as regras de permissão. Com ele, o script só arranca para comandos git push, em vez de arrancar para todos os comandos de shell.
Quando o Claude tentou git push --force origin master no nosso teste, o comando nunca correu e o Claude recebeu:
Resultado
PreToolUse:Bash hook error: ["$CLAUDE_PROJECT_DIR"/.claude/hooks/block-force-push.sh]: Blocked by hook: force push is not allowed in this project. Push normally or ask the user.Um hook que bloqueia funciona em todos os modos de permissão, incluindo o modo Ignorar permissões (bypassPermissions). O contrário não é verdade: um hook que responde "allow" não passa por cima de uma regra de bloqueio.
Atenção
Tal como uma regra de permissão de Bash, este script só lê o texto do comando. Um comando escrito de forma invulgar pode escapar a um padrão. Usa hooks como este para apanhar enganos. Para um limite que tem mesmo de se cumprir, junta uma regra de bloqueio à sandbox (/sandbox).
Exemplo 3: verificar a sintaxe do PHP depois de cada edição
Este hook corre php -l em cada ficheiro PHP que o Claude cria ou edita. Se a verificação falhar, o Claude lê o erro e corrige o ficheiro logo a seguir.
Escreve o script
.claude/hooks/php-lint.sh#!/bin/bash # After Claude edits a PHP file, check its syntax and report errors back to Claude. file=$(jq -r '.tool_input.file_path') if ! output=$(php -l "$file" 2>&1); then echo "PHP syntax check failed for $file:" >&2 echo "$output" >&2 exit 2 fi exit 0Torna-o executável
Terminal · o teu utilizadorchmod +x .claude/hooks/php-lint.shRegista-o para o Write e para o Edit
.claude/settings.json{ "hooks": { "PostToolUse": [ { "matcher": "Edit|Write", "hooks": [ { "type": "command", "if": "Write(*.php)", "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/php-lint.sh" }, { "type": "command", "if": "Edit(*.php)", "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/php-lint.sh" } ] } ] } }
Atenção
Uma condição if vale para uma só ferramenta. A nossa primeira versão tinha só Edit(*.php) e, quando o Claude criou um ficheiro novo com a ferramenta Write, a verificação nunca correu. O registo de depuração dizia Skipping hook due to if condition "Edit(*.php)" not matching. O Claude cria ficheiros novos com o Write e altera os que já existem com o Edit, por isso precisas de um de cada.
Com os dois em vigor, pedimos ao Claude que criasse um ficheiro sem um ponto e vírgula. O hook respondeu:
Resultado
PHP syntax check failed for /home/tu/projeto/hello.php:
PHP Parse error: syntax error, unexpected end of file, expecting "," or ";" in /home/tu/projeto/hello.php on line 1
Errors parsing /home/tu/projeto/hello.phpO Claude acrescentou o ponto e vírgula com a ferramenta Edit, o hook correu outra vez sem dizer nada, e o ficheiro passou.
O mesmo esquema serve para qualquer verificador ou formatador: python -m py_compile, shellcheck, prettier --write, e por aí fora.
Exemplo 4: saber quando o Claude está à tua espera
Quando o Claude precisa da tua permissão e estás noutra janela, um aviso poupa-te a ir ver o terminal de poucos em poucos minutos. Põe este nas tuas definições de utilizador, para funcionar em todos os projetos.
Num ambiente de trabalho Linux:
{
"hooks": {
"Notification": [
{
"matcher": "permission_prompt",
"hooks": [
{
"type": "command",
"command": "notify-send 'Claude Code' 'Claude Code needs your attention'"
}
]
}
]
}
}Num Mac, usa antes este comando:
osascript -e 'display notification "Claude Code needs your attention" with title "Claude Code"'permission_promptcorre quando um pedido de permissão já está à espera há cerca de seis segundos.idle_promptcorre quando o Claude acabou de responder há cerca de um minuto e ainda não escreveste nada.- Um filtro vazio,
"", corre em todos os tipos de aviso.
Nota
Testámos o evento num servidor, que não tem ambiente de trabalho, por isso o hook de teste escreveu num ficheiro em vez de mostrar um aviso. Correu enquanto o pedido de permissão estava à espera, e o JSON que recebeu trazia a mensagem Claude needs your permission. Os comandos notify-send e osascript são os da documentação oficial. O notify-send precisa de uma sessão gráfica, por isso não faz nada por SSH. Num Mac, o primeiro aviso pode obrigar-te a autorizar as notificações do Editor de Scripts nas Definições do Sistema.
Os hooks correm com as tuas permissões
Um hook de comando corre como tu, com acesso a tudo o que a tua conta de utilizador alcança. Antes de acrescentares um, lê o que ele faz.
Os hooks do .claude/settings.json de um projeto vêm de quem escreveu o repositório, e há duas coisas dos nossos testes que convém saber:
- Numa sessão interativa, o Claude Code segura os hooks até confiares na pasta. Mas a caixa de confiança que vimos mostrava a regra de permissão já aprovada da pasta e não dizia nada sobre os hooks. Antes de confiares num repositório que não foste tu a escrever, abre o
.claude/settings.jsondele e lê tu mesmo a secçãohooks. - O
claude -pnunca mostra a caixa de confiança e trata a pasta como de confiança para os hooks. No nosso teste, ignorou a regra de autorizar do projeto porque a pasta não era de confiança, mas correu os hooks do projeto na mesma. Para correr oclaude -pno repositório de outra pessoa sem os hooks dela, acrescenta--setting-sources user. Assim o Claude Code só lê as tuas definições, e os hooks e as regras do projeto ficam de fora. Testámos: com esta opção, o hook de registo do projeto não correu.
Quando um hook não funciona
- Testa primeiro o script à mão, com uma linha de JSON, como no exemplo 2. Confirma o código de saída com
echo $?. - Torna os scripts executáveis com
chmod +x. Sem isso, o script não corre. - Confirma o filtro. Os filtros distinguem maiúsculas de minúsculas:
bashnão éBash. jq: command not found: instala ojq, ou escreve o hook em Python.- Vê o que aconteceu. Carrega em
Ctrl+Opara a vista da transcrição, onde aparecem as mensagens de bloqueio e os erros dos hooks. Um hook que corre bem não mostra ali nada. - Lê o registo de depuração. Arranca o Claude Code com
claude --debug-file /tmp/claude.log, e o registo mostra que hooks corresponderam, quais foram saltados e porquê, e o que cada um escreveu.
Fontes
- Automate actions with hooks, documentação do Claude Code (em inglês)
- Hooks reference, documentação do Claude Code (em inglês)
- Configure permissions, documentação do Claude Code (em inglês)
- jq
Verificado em
Claude Code 2.1.280 em AlmaLinux 9.8, com jq 1.6 e PHP 8. Os quatro hooks foram testados num projeto git descartável: os exemplos 1 a 3 em execuções com claude -p, e o exemplo 4 e a caixa de confiança numa sessão interativa. As mensagens acima foram copiadas dessas sessões, com o caminho do projeto trocado por /home/tu/projeto.