Rode seus próprios comandos automaticamente com os hooks do Claude Code
Hooks são comandos de shell que o Claude Code roda em momentos fixos, como antes de um comando ou depois de uma edição. Registre o que o Claude roda, bloqueie o que ele nunca deve rodar, confira cada arquivo que ele edita e receba um aviso quando ele estiver esperando por você.
Conferido na documentação oficial em
Nesta página9 seções
- 01O que é um hook
- 02Onde ficam os hooks
- 03Exemplo 1: registrar todos os comandos
- 04Exemplo 2: bloquear force push
- 05Exemplo 3: conferir a sintaxe do PHP depois de cada edição
- 06Exemplo 4: saber quando o Claude está esperando por você
- 07Os hooks rodam com as suas permissões
- 08Quando um hook não funciona
- 09Fontes
Você pode escrever no seu CLAUDE.md "sempre confira a sintaxe do PHP depois de editar um arquivo", e o Claude costuma fazer isso. Costuma. Um hook dá certeza. É um comando seu que o Claude Code roda toda vez que chega um determinado momento, não importa o que o Claude decida.
Este guia explica como os hooks funcionam e depois monta quatro, passo a passo: um registro de todos os comandos que o Claude roda, um bloqueio de force push, uma checagem de sintaxe depois de cada edição em PHP e um aviso quando o Claude está esperando por você. Os quatro foram testados em sessões reais.
O que é um hook
Um hook tem três partes:
- Um evento: o momento em que ele roda. Por exemplo
PreToolUse(antes de o Claude usar uma ferramenta),PostToolUse(depois que uma ferramenta roda sem erro) ouNotification(quando o Claude Code precisa da sua atenção). - Um filtro (
matcher): os casos em que ele vale. Nos eventos de ferramentas, é o nome da ferramenta, comoBashouEdit|Write. - Um comando: o que roda. Normalmente um comando de shell ou um script.
Quando o evento acontece, o Claude Code manda os detalhes para o seu comando em JSON, pela entrada padrão. Quando a ferramenta é o Bash, isso inclui o comando que o Claude quer rodar:
{
"session_id": "abc123",
"cwd": "/home/voce/projeto",
"hook_event_name": "PreToolUse",
"tool_name": "Bash",
"tool_input": {
"command": "npm test"
}
}O seu comando lê o que precisa (os exemplos aqui usam o jq), faz o trabalho dele e responde com o código de saída:
- Código 0: nada contra. O Claude Code segue normalmente. No
PreToolUseisso não aprova nada: as regras de permissão normais continuam valendo. - Código 2: um erro que bloqueia. O que o script escreve na saída de erros volta como motivo. Um hook
PreToolUsebloqueia a chamada da ferramenta, e o Claude lê a sua mensagem. Um hookPostToolUsenão consegue desfazer nada, porque a ferramenta já rodou, mas o Claude lê a mensagem mesmo assim e pode corrigir o problema. - Qualquer outro código: um erro que não bloqueia. A ação segue, e aparece um aviso curto
hook error.
Os eventos que você vai usar mais:
PreToolUse: antes de uma chamada de ferramenta. O único desta lista que consegue parar uma chamada de ferramenta antes de ela rodar.PostToolUse: depois que uma chamada de ferramenta roda sem erro.Notification: quando o Claude Code está esperando a sua permissão ou uma resposta sua.SessionStart: quando uma sessão começa ou é retomada. O texto simples que o comando imprimir entra no contexto do Claude.UserPromptSubmit: quando você envia uma mensagem, antes de o Claude vê-la.Stop: quando o Claude termina a resposta.
A lista completa, com mais de 30 eventos, está na referência dos hooks, nas fontes no final.
Onde ficam os hooks
Os hooks vão nos mesmos arquivos de configurações que as regras de permissão, dentro de uma chave hooks:
~/.claude/settings.json: todos os projetos da sua máquina..claude/settings.jsonem um projeto: compartilhado com todo mundo, se você colocar no git..claude/settings.local.jsonem um projeto: só você.
Digite /hooks em uma sessão para ver todos os hooks em vigor, agrupados por evento, e o arquivo de onde vem cada um. O menu é só para consulta. Para adicionar ou mudar um hook, edite o arquivo de configurações, ou peça para o Claude fazer isso. Normalmente o Claude Code percebe a mudança sozinho, sem reiniciar.
Cada exemplo abaixo é um arquivo de configurações completo. Se o seu já tiver uma chave hooks, junte o novo evento aos que já estão lá, em vez de substituir o bloco inteiro.
Exemplo 1: registrar todos os comandos
Este hook adiciona a um arquivo do projeto cada comando de shell que o Claude roda:
{
"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, então o registro sempre vai para o mesmo lugar, não importa onde o Claude esteja trabalhando.
Nota
O PostToolUse só roda depois que uma chamada dá certo. No nosso teste, um comando bloqueado por outro hook nunca chegou ao registro. Para registrar também as chamadas que falham, adicione o mesmo hook em PostToolUseFailure.
Exemplo 2: bloquear force push
Um hook PreToolUse roda antes da verificação de permissões, então ele consegue parar um comando mesmo em modos que deixariam o comando rodar.
Escreva 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 0Deixe o script executável
Terminal · seu usuáriochmod +x .claude/hooks/block-force-push.shTeste na mão
Mande para ele o mesmo tipo de JSON que o Claude Code mandaria e confira o código de saída:
Terminal · seu usuárioecho '{"tool_input":{"command":"git push --force origin main"}}' | .claude/hooks/block-force-push.sh; echo "exit=$?"Saída
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.Registre o hook
.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 das regras de permissão. Com ele, o script só roda para comandos git push, em vez de rodar para todo comando de shell.
Quando o Claude tentou git push --force origin master no nosso teste, o comando não rodou, e o Claude recebeu:
Saída
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, inclusive no modo Ignorar permissões (bypassPermissions). O contrário não vale: um hook que responde "allow" não passa por cima de uma regra de bloqueio.
Atenção
Assim como uma regra de permissão de Bash, este script só lê o texto do comando. Um comando escrito de um jeito diferente pode escapar de um padrão. Use hooks como este para pegar erros. Para um limite que precisa valer de verdade, junte uma regra de bloqueio com a sandbox (/sandbox).
Exemplo 3: conferir a sintaxe do PHP depois de cada edição
Este hook roda php -l em cada arquivo PHP que o Claude cria ou edita. Se a checagem falhar, o Claude lê o erro e corrige o arquivo na hora.
Escreva 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 0Deixe o script executável
Terminal · seu usuáriochmod +x .claude/hooks/php-lint.shRegistre o hook 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 ferramenta só. A nossa primeira versão tinha só Edit(*.php) e, quando o Claude criou um arquivo novo com a ferramenta Write, a checagem nunca rodou. O log de depuração dizia Skipping hook due to if condition "Edit(*.php)" not matching. O Claude cria arquivos novos com o Write e altera os que já existem com o Edit, então você precisa de um de cada.
Com os dois no lugar, pedimos ao Claude para criar um arquivo sem um ponto e vírgula. O hook respondeu:
Saída
PHP syntax check failed for /home/voce/projeto/hello.php:
PHP Parse error: syntax error, unexpected end of file, expecting "," or ";" in /home/voce/projeto/hello.php on line 1
Errors parsing /home/voce/projeto/hello.phpO Claude adicionou o ponto e vírgula com a ferramenta Edit, o hook rodou de novo sem dizer nada, e o arquivo passou.
O mesmo esquema serve para qualquer verificador ou formatador: python -m py_compile, shellcheck, prettier --write e assim por diante.
Exemplo 4: saber quando o Claude está esperando por você
Quando o Claude precisa da sua permissão e você está em outra janela, um aviso poupa você de olhar o terminal a cada poucos minutos. Coloque este hook nas suas configurações de usuário, para funcionar em todos os projetos.
Em um desktop Linux:
{
"hooks": {
"Notification": [
{
"matcher": "permission_prompt",
"hooks": [
{
"type": "command",
"command": "notify-send 'Claude Code' 'Claude Code needs your attention'"
}
]
}
]
}
}Em um Mac, use este comando no lugar:
osascript -e 'display notification "Claude Code needs your attention" with title "Claude Code"'permission_promptbate quando um pedido de permissão já está esperando há uns seis segundos.idle_promptbate quando o Claude terminou de responder há mais ou menos um minuto e você ainda não digitou nada.- Com um filtro vazio,
"", o hook roda em todos os tipos de aviso.
Nota
Testamos o evento em um servidor sem interface gráfica, então o hook de teste escreveu em um arquivo em vez de mostrar um aviso. Ele rodou enquanto o pedido de permissão estava esperando, 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, então não faz nada por SSH. No Mac, o primeiro aviso pode exigir que você libere as notificações do Editor de Scripts nos Ajustes do Sistema.
Os hooks rodam com as suas permissões
Um hook de comando roda como você, com acesso a tudo o que a sua conta de usuário alcança. Antes de adicionar um, leia o que ele faz.
Os hooks do .claude/settings.json de um projeto vêm de quem escreveu o repositório, e duas coisas dos nossos testes são importantes:
- Em uma sessão interativa, o Claude Code não roda os hooks até você confiar 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 confiar em um repositório que não foi você que escreveu, abra o
.claude/settings.jsondele e leia você mesmo a seçãohooks. - O
claude -pnunca mostra a caixa de confiança e trata a pasta como confiável para os hooks. No nosso teste, ele ignorou a regra de liberar do projeto porque a pasta não era confiável, mas rodou os hooks do projeto mesmo assim. Para rodar oclaude -pno repositório de outra pessoa sem os hooks dela, adicione--setting-sources user. Assim o Claude Code lê só as suas configurações, e os hooks e as regras do projeto ficam de fora. Testamos: com essa opção, o hook de registro do projeto não rodou.
Quando um hook não funciona
- Teste o script na mão primeiro, com uma linha de JSON, como no exemplo 2. Confira o código de saída com
echo $?. - Deixe os scripts executáveis com
chmod +x. Sem isso, o script não roda. - Confira o filtro. Os filtros diferenciam maiúsculas de minúsculas:
bashnão éBash. jq: command not found: instale ojq, ou escreva o hook em Python.- Veja o que aconteceu. Aperte
Ctrl+Opara abrir a transcrição, onde aparecem as mensagens de bloqueio e os erros dos hooks. Um hook que roda sem erro não mostra nada ali. - Leia o log de depuração. Inicie o Claude Code com
claude --debug-file /tmp/claude.log, e o log mostra quais hooks bateram, quais foram pulados e por quê, e o que cada um imprimiu.
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
Conferido em
Claude Code 2.1.280 no AlmaLinux 9.8, com jq 1.6 e PHP 8. Os quatro hooks foram testados em um 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 em uma sessão interativa. As mensagens acima foram copiadas dessas sessões, com o caminho do projeto trocado por /home/voce/projeto.