Hooks no Claude Code: como automatizar lint, formatação e bloqueios antes e depois do agente editar o código

Os hooks no Claude Code são comandos shell, endpoints HTTP, prompts ou subagentes que o próprio Claude Code executa automaticamente em pontos do ciclo de vida da sessão, como antes e depois de uma ferramenta rodar. A configuração mora em ~/.claude/settings.json (global) ou .claude/settings.json na raiz do projeto, e o comando /hooks lista tudo agrupado por evento. Com PostToolUse você formata o arquivo logo após a edição; com PreToolUse você bloqueia mudança em arquivo sensível antes dela acontecer. A diferença pro prompt é simples: prompt é pedido, hook é garantia
Fala aí, beleza? Quantas vezes você escreveu "sempre formata o arquivo depois de editar" e o agente simplesmente… não formatou?
Pois é. Instrução no prompt é PEDIDO, hook é garantia
O que está no prompt o modelo pode ignorar, esquecer no meio de uma tarefa longa ou reinterpretar do jeito dele; o que está amarrado num hook o Claude Code dispara sozinho, toda vez que o gatilho acontece, sem depender da boa vontade da IA
E o que são hooks, afinal? São comandos shell, endpoints HTTP, prompts ou subagentes definidos por você, que o Claude Code executa automaticamente em pontos específicos do ciclo de vida da sessão, conforme a documentação oficial de hooks
Bora ver na prática?
O que você precisa antes de configurar um hook
A lista é curta, mas cada item evita uma dor de cabeça depois
- Claude Code instalado e um projeto aberto pra testar
- O arquivo de settings certo pro escopo que você quer: <code>~/.claude/settings.json</code> vale em qualquer projeto (global) e <code>.claude/settings.json</code> na raiz do projeto é o nível de projeto, que sobrepõe o global. Tá no Windows? O <code>~/.claude</code> resolve pra <code>%USERPROFILE%\.claude</code>
- <code>jq</code> disponível pros exemplos oficiais, porque o hook do tipo <code>command</code> recebe o JSON do evento pelo stdin e alguém precisa extrair o campo dali
- Prettier via <code>npx</code>, no caso específico do exemplo de formatação
Formação Claude Code
Domine Claude Code do absoluto zero até o avançado
- 120 aulas
- 4 projetos
- 9h 45min
Uma coisa MUITO boa: editar o arquivo de settings não exige reiniciar a sessão
O Claude Code observa os arquivos de settings e recarrega quando eles mudam, e edições em chaves como <code>permissions</code> e <code>hooks</code> já valem na sessão em andamento
E se você quiser ver o que já existe configurado antes de mexer em qualquer coisa, roda <code>/hooks</code> dentro do Claude Code: ele lista todos os hooks configurados agrupados por evento, e também atualiza o arquivo de settings e recarrega a configuração 🙂
Como configurar um hook no Claude Code passo a passo
- Rode <code>/hooks</code> antes de escrever qualquer JSON
É o mapa do terreno: você vê o que já está registrado e, principalmente, entende que hook não é uma lista solta, é uma coisa agrupada POR EVENTO
- Entenda os três níveis de aninhamento do JSON
A estrutura tem sempre a mesma cara: evento de hook (ex: <code>PreToolUse</code>) > grupo de matcher (o filtro) > handler (o que efetivamente roda)
O campo <code>hooks</code> fica no topo do <code>settings.json</code>, no mesmo nível de <code>permissions</code>
Se você conhece a estrutura de scripts do <code>package.json</code>, a lógica de aninhamento é parecida: uma chave no topo, e dentro dela a organização por tipo
- Escolha o matcher
O <code>matcher</code> filtra por nome da ferramenta e aceita valores como <code>Bash</code>, <code>Edit</code>, <code>Write</code>, padrões como <code>"Edit|Write"</code> ou vazio pra pegar todas
O erro comum deste passo: matchers são case-sensitive
Escreveu <code>edit</code> em minúscula? O hook não dispara e você fica achando que o recurso está quebrado
Outro erro comum aqui é esperar filtro por extensão de arquivo. Não é o papel do matcher: a recomendação da doc é ler o JSON recebido e aplicar a sua própria condição dentro do script
- Comece pela formatação automática com <code>PostToolUse</code>
Esse é o hook de entrada, o mais fácil de acertar. No <code>.claude/settings.json</code> do projeto:
<pre><code class="language-json">{ "hooks": { "PostToolUse": [ { "matcher": "Edit|Write", "hooks": [ { "type": "command", "command": "jq -r ‘.tool_input.file_path’ | xargs npx prettier –write" } ] } ] } } </code></pre>
O que acontece aqui: o Claude edita ou escreve um arquivo, o evento dispara, o <code>jq</code> pega o caminho do arquivo de dentro do JSON do evento e joga pro Prettier formatar
O erro comum deste passo é achar que não funcionou
Quando o hook dá certo, o Claude Code não mostra NADA na conversa. Silêncio é sucesso, se liga nisso 😀
- Suba pro bloqueio de arquivos sensíveis com <code>PreToolUse</code>
Agora a parte que realmente muda o jogo: impedir que o agente encoste em <code>.env</code>, <code>package-lock.json</code> ou <code>.git/</code>
O exemplo oficial usa um script <code>protect-files.sh</code> que lê o JSON do stdin, extrai o caminho, compara com os padrões protegidos, escreve o motivo no stderr e sai com <code>exit 2</code> pra bloquear:
<pre><code class="language-bash">#!/bin/bash file_path=$(jq -r ‘.tool_input.file_path // empty’)
for pattern in ".env" "package-lock.json" ".git/"; do case "$file_path" in "$pattern") echo "Arquivo protegido: $file_path" >&2 exit 2 ;; esac done
exit 0 </code></pre>
E o registro no <code>.claude/settings.json</code>, com matcher pras ferramentas de escrita:
<pre><code class="language-json">{ "hooks": { "PreToolUse": [ { "matcher": "Edit|Write", "hooks": [ { "type": "command", "command": "./protect-files.sh" } ] } ] } } </code></pre>
Repare no detalhe do <code>// empty</code> no <code>jq</code>: é ele que evita o script explodir quando o campo não vem
O bom é que o Claude recebe a explicação do bloqueio e ajusta a abordagem, em vez de ficar batendo na mesma porta
- Entenda a semântica dos códigos de saída
Essa parte é onde mora quase toda confusão
- <code>exit 0</code>: o hook não objeta e a ação segue. Em <code>PreToolUse</code> isso NÃO aprova a chamada, o fluxo normal de permissão continua valendo
- <code>exit 2</code>: erro bloqueante, e o texto do stderr volta pro Claude como mensagem de erro. Em <code>PreToolUse</code> bloqueia a chamada da ferramenta, em <code>UserPromptSubmit</code> rejeita o prompt
- Saída JSON só é processada em <code>exit 0</code>. Com <code>exit 2</code> o JSON é ignorado
O erro comum deste passo: achar que <code>exit 2</code> é a ÚNICA forma de negar
O <code>PreToolUse</code> também nega via JSON, assim:
<pre><code class="language-json">{ "hookSpecificOutput": { "hookEventName": "PreToolUse", "permissionDecision": "deny", "permissionDecisionReason": "Arquivo protegido pelo hook do projeto" } } </code></pre>
E aqui vai o detalhe mais forte de todos: hooks <code>PreToolUse</code> disparam ANTES da checagem de modo de permissão, em todos os modos, incluindo <code>dontAsk</code>
Um deny bloqueia a ferramenta mesmo em <code>bypassPermissions</code> ou com <code>–dangerously-skip-permissions</code>
Ou seja: o guardrail (limite) não é contornável trocando o modo de permissão
Quais gatilhos existem e o que dá para automatizar com cada um
A referência oficial cita um monte de eventos: <code>PreToolUse</code>, <code>PostToolUse</code>, <code>PostToolUseFailure</code>, <code>PostToolBatch</code>, <code>Notification</code>, <code>UserPromptSubmit</code>, <code>UserPromptExpansion</code>, <code>SessionStart</code>, <code>SessionEnd</code>, <code>Stop</code>, <code>StopFailure</code>, <code>SubagentStart</code>, <code>SubagentStop</code>, <code>PreCompact</code>, <code>PostCompact</code>, <code>PermissionRequest</code>, <code>PermissionDenied</code>, <code>Setup</code>, <code>TeammateIdle</code>, <code>TaskCreated</code>, <code>TaskCompleted</code>, <code>Elicitation</code>, <code>ElicitationResult</code>, <code>ConfigChange</code>, <code>DirectoryAdded</code>, <code>WorktreeCreate</code>, <code>WorktreeRemove</code>, <code>InstructionsLoaded</code>, <code>CwdChanged</code>, <code>FileChanged</code> e <code>MessageDisplay</code>
É completão. Mas na real você começa com um punhado deles:
| Evento | Quando dispara | O que dá pra fazer |
|---|---|---|
| <code>PreToolUse</code> | Antes da ferramenta rodar | Bloquear arquivo sensível, negar via JSON, reescrever argumentos |
| <code>PostToolUse</code> | Depois da ferramenta rodar | Formatar o arquivo editado, avisar o modelo do resultado |
| <code>UserPromptSubmit</code> | Ao enviar um prompt, antes do processamento | Rejeitar prompt fora de política |
| <code>SessionStart</code> | Quando a sessão começa ou é retomada | Injetar contexto (o stdout entra como contexto que o Claude enxerga) |
| <code>Stop</code> e <code>SubagentStop</code> | Quando o Claude ou um subagente termina de responder | Fechamento de ciclo, checagem final |
| <code>PermissionRequest</code> | Quando o Claude Code vai pedir permissão | Aprovar ou negar no lugar do usuário |
| <code>PreCompact</code> | Antes da compactação de contexto | Preparar o terreno antes do contexto encolher |
| <code>Notification</code> | Quando o Claude Code manda uma notificação | Encaminhar o aviso pra onde você quiser |
Um caso que passa despercebido: o <code>PreToolUse</code> não serve só pra dizer sim ou não
Ele também REESCREVE os argumentos da ferramenta antes da execução, pelo campo <code>updatedInput</code> dentro de <code>hookSpecificOutput</code>
E hook não é só shell, viu?
São cinco tipos de handler:
- <code>command</code>: roda um comando shell
- <code>prompt</code>: manda um prompt pra um modelo Claude decidir sim ou não em turno único
- <code>agent</code>: sobe um subagente que pode usar Read, Grep e Glob antes de decidir (marcado como experimental na doc). Se a ideia de delegar pedaço de trabalho pra outro agente te interessa, vale ver como usar subagents no Claude Code
- <code>http</code>: faz POST dos dados do evento pra uma URL, útil pra mandar telemetria da sessão pro seu próprio serviço
- <code>mcp_tool</code>: chama ferramenta de um servidor MCP já conectado
Vale separar as coisas: hook é automação DENTRO da sessão local, na sua máquina
Pra automação no pipeline, com o agente rodando no repositório remoto, a conversa é outra, e passa por rodar o Claude Code no GitHub Actions
Hook não funcionou como esperado: causas comuns e como resolver
Se liga nos tropeços mais fáceis de cair
O hook simplesmente não dispara
Causa mais provável: caixa errada no <code>matcher</code>
Eles são case-sensitive, então <code>Edit</code> funciona e <code>edit</code> não
Coloquei <code>exit 2</code> no <code>PostToolUse</code> e a edição não foi desfeita
E não vai ser mesmo
Em <code>PostToolUse</code> e <code>PostToolUseFailure</code> o <code>exit 2</code> não desfaz nada: ele só expõe o stderr ao modelo, que enxerga o aviso com a ferramenta JÁ executada
Bloqueio de verdade é <code>PreToolUse</code>. Ponto
O <code>exit 2</code> no <code>SessionStart</code> não surtiu efeito nenhum
Em <code>SessionStart</code>, <code>Setup</code> e <code>SubagentStart</code> o stderr do <code>exit 2</code> aparece no transcript como aviso de erro de hook, o Claude não vê e a sessão segue normalmente
Meu JSON de saída foi ignorado
Provavelmente você saiu com <code>exit 2</code>
Saída JSON só é processada em <code>exit 0</code>. Escolha um caminho: ou bloqueia pelo código de saída, ou responde JSON com <code>exit 0</code>
O hook foi cortado no meio
Os padrões de timeout são estes:
- 600 segundos (10 minutos) pros tipos <code>command</code>, <code>http</code> e <code>mcp_tool</code> na maioria dos eventos
- 30 segundos no <code>UserPromptSubmit</code> pra esses mesmos tipos
- 60 segundos pro tipo <code>agent</code>, com até 50 turnos de uso de ferramenta
Dá pra ajustar por hook no campo <code>timeout</code>, em segundos
Dois <code>updatedInput</code> brigando entre si
Quando vários hooks <code>PreToolUse</code> devolvem <code>updatedInput</code>, vence o ÚLTIMO a terminar
E como os hooks rodam em paralelo, a ordem é não determinística. Tome cuidado ao ter mais de um hook reescrevendo o mesmo argumento
Parece que o mesmo comando rodou duas vezes
Provavelmente não rodou
Quando um evento dispara, todos os hooks que casam rodam em paralelo, e comandos de hook idênticos são deduplicados automaticamente: se o mesmo handler estiver em mais de um arquivo de settings, ele roda uma vez só (a cópia vinda de um plugin ou skill continua sendo contada à parte)
Prevenção, no geral
Rode <code>/hooks</code> sempre que a coisa parecer estranha, pra conferir o que está de fato registrado
E não esquece do óbvio: a documentação oficial coloca a revisão do código e dos comandos propostos, antes da aprovação, como responsabilidade SUA. Hook roda automático, então um comando mal escrito ali roda automático também
Conclusão
A ideia central cabe numa linha: instrução no prompt é pedido, hook é garantia
Se você já cansou de repetir a mesma regra pro agente e ver ele esquecendo no meio do caminho, os hooks no Claude Code são exatamente a ferramenta que faltava
O próximo passo concreto? Comece pequeno
Um único hook <code>PostToolUse</code> de formatação no <code>.claude/settings.json</code> do projeto, confere com <code>/hooks</code>, edita um arquivo qualquer e vê o Prettier agindo sozinho
Depois que isso estiver redondo, aí sim parte pros bloqueios em <code>PreToolUse</code>, que é onde mora o poder de verdade…
até o próximo post! =)
Perguntas frequentes
Hook em PreToolUse consegue bloquear mesmo rodando com –dangerously-skip-permissions?
Sim. Hooks PreToolUse disparam antes da checagem de modo de permissão, em todos os modos, incluindo dontAsk e bypassPermissions. Um deny devolvido pelo hook via permissionDecision bloqueia a ferramenta mesmo com –dangerously-skip-permissions ativo.
Qual a diferença entre um hook do tipo command e um hook do tipo agent?
O tipo command roda um comando shell e recebe o JSON do evento pelo stdin. O tipo agent sobe um subagente que pode usar Read, Grep e Glob antes de decidir, mas é marcado como experimental na documentação oficial. Existem ainda os tipos prompt, que manda um prompt pra um modelo Claude decidir sim ou não em turno único, http, que faz POST dos dados do evento pra uma URL, e mcp_tool, que chama ferramenta de um servidor MCP já conectado.
Dá pra usar hooks pra reescrever os argumentos de uma ferramenta antes dela rodar?
Dá. O PreToolUse aceita o campo updatedInput dentro de hookSpecificOutput, e esse campo substitui os argumentos da ferramenta antes da execução. Se mais de um hook PreToolUse devolver updatedInput ao mesmo tempo, vence o último a terminar, porque os hooks rodam em paralelo e a ordem não é determinística.
Quanto tempo um hook pode demorar antes de estourar o timeout?
O padrão é 600 segundos, ou seja 10 minutos, para hooks do tipo command, http e mcp_tool na maioria dos eventos, exceto no UserPromptSubmit, que usa 30 segundos. Hooks do tipo agent têm padrão de 60 segundos e limite de até 50 turnos de uso de ferramenta. Esse timeout é configurável por hook, no campo timeout, em segundos.
Um hook em PostToolUse consegue desfazer uma edição que já foi feita?
Não. Em PostToolUse e PostToolUseFailure o exit 2 não desfaz nada, ele só expõe o stderr pro modelo, que enxerga o aviso mesmo com a ferramenta já executada. Por isso impedir a ação antes de acontecer é papel do PreToolUse, não do PostToolUse.
Como ver quais hooks já estão configurados sem abrir o settings.json na mão?
Rodando /hooks dentro do Claude Code. O comando lista todos os hooks configurados agrupados por evento, e de quebra também atualiza o arquivo de settings e recarrega a configuração.
Formações
Formação Vibe Coding
Do Prompt ao Produto: Crie Software Real com IA
- 474 aulas
- 20 projetos
- 39h 27min
Blog | Mais populares
Bateu o limite de uso do Claude Code? Como retomar a tarefa sem refazer tudo
Bateu o limite de uso do Claude Code? Veja como retomar a tarefa de onde parou com /usage, CLAUDE.md e --continue, sem refazer nada.
Como pagar o Claude Code no Brasil: cartão, dólar, IOF e quanto fica em reais
Claude Code preço Brasil na prática: câmbio, IOF de 3,5% e quanto fica na fatura. Planos Pro e Max convertidos em reais e como pagar com cartão.

O que é o JEV e para que ele serve no Claude Code?
O que é JEV no Claude Code? Entenda o modelo da TypeSafe AI, como instalar a skill e quanto custa por milhão de tokens.
