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

hooks no Claude Code automatizando lint e formatação antes e depois da edição do agente
Resposta rápida

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
Formação Recomendada

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

  1. 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

  1. 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

  1. 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

  1. 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 😀

  1. 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" &gt;&amp;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

  1. 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.



Escrito por | Matheus Battisti

Matheus Battisti
Fundador da Hora de Codar

Programador apaixonado pelo mundo das tecnologias, sempre buscando em aprender e se aprofundar em linguagens, frameworks e o que mais for necessário para executar um bom trabalho. Agora tem uma nova missão que é de passar seu conhecimento adiante para formar novos programadores e especializar mais os que já são.

Subscribe
Notify of
guest

0 Comentários
Oldest
Newest Most Voted

Formações

Formação Vibe Coding

Formação Vibe Coding

Do Prompt ao Produto: Crie Software Real com IA

  • 474 aulas
  • 20 projetos
  • 39h 27min

Blog | Mais populares