Hooks do Claude Code: guia prático com exemplos

Como funcionam os hooks do Claude Code: eventos, settings.json, matchers, exit codes e saída JSON, mais hooks prontos que formatam, bloqueiam e avisam você.

Solviera Teknoloji 11 min de leitura

Hooks do Claude Code são comandos que o Claude Code executa sozinho em pontos fixos de uma sessão: antes de uma chamada de ferramenta, depois de editar um arquivo, quando um prompt é enviado, quando o Claude para ou precisa da sua atenção. Você os define na chave hooks do settings.json. Um hook pode adicionar contexto, formatar um arquivo ou bloquear uma ação saindo com código 2 ou retornando uma decisão em JSON. Diferente das instruções no CLAUDE.md, um hook sempre roda, porque quem o executa é o harness, não o modelo.

Este guia cobre os eventos que importam, o formato da configuração, matchers, exit codes e saída JSON, três hooks que você pode copiar hoje e as notas de segurança para ler antes de adicionar qualquer um. As informações vêm da referência de hooks e do guia de hooks oficiais. Se você só quer a definição curta, veja o verbete hooks do Claude Code no glossário.

Por que usar hooks em vez de instruções

Você pode escrever “sempre rode o Prettier depois de editar” no CLAUDE.md, e o Claude normalmente vai fazer isso. O problema é o “normalmente”. Instruções são conselhos que o modelo pesa contra todo o resto do contexto. Hooks são código que roda toda vez que o evento dispara.

Isso faz dos hooks a ferramenta certa para três tipos de regra:

  • Coisas que sempre devem acontecer, como formatação, lint ou log.
  • Coisas que nunca devem acontecer, como editar o .env ou rodar rm -rf na pasta errada.
  • Coisas que você quer saber, como o Claude esperando uma resposta de permissão enquanto você está em outra janela.

Deixe conselhos de estilo e conhecimento do projeto no CLAUDE.md e coloque regras rígidas em hooks.

Eventos de hook que você vai usar de verdade

A referência lista mais de trinta eventos, e a lista cresce a cada versão. A maioria das configurações reais usa um punhado:

EventoQuando disparaPode bloquear?
PreToolUseAntes de uma chamada de ferramenta rodarSim, bloqueia a chamada
PostToolUseDepois que uma chamada de ferramenta dá certoNão, a ferramenta já rodou; o stderr vai para o Claude
UserPromptSubmitQuando você envia um promptSim, o prompt nunca chega ao Claude
NotificationQuando o Claude Code envia uma notificação, como um pedido de permissão ou espera ociosaNão
StopQuando o Claude termina a respostaSim, o Claude continua trabalhando
SubagentStart / SubagentStopQuando um subagente inicia ou terminaSó SubagentStop
SessionStartNa inicialização, ao retomar, em /clear ou na compactaçãoNão, mas o stdout vira contexto
PreCompactAntes de o contexto ser compactadoSim, bloqueia a compactação
PermissionRequestQuando um diálogo de permissão está prestes a aparecerSó via JSON, não com exit 2
SessionEndQuando a sessão terminaNão

PreToolUse e PostToolUse cobrem a maioria das necessidades. Notification e Stop são os de “me avise quando terminar ou travar”.

Como os hooks são configurados no settings.json

Os hooks ficam nos mesmos arquivos de configuração que as permissões:

ArquivoEscopoCompartilhado via git?
~/.claude/settings.jsonTodos os seus projetosNão
.claude/settings.jsonUm projetoSim
.claude/settings.local.jsonUm projeto, só vocêNão (no gitignore)
Configurações de política gerenciadaOrganização inteiraDefinidas por admins

Plugins podem trazer hooks em hooks/hooks.json, e skills e subagentes podem definir hooks no frontmatter.

A estrutura tem três níveis: o evento, um grupo de matcher e uma lista de handlers.

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "jq -r '.tool_input.file_path' | xargs npx prettier --write",
            "timeout": 30
          }
        ]
      }
    ]
  }
}

O type de um handler pode ser command (um comando de shell), http (um POST para uma URL), mcp_tool (uma ferramenta de um servidor MCP conectado), prompt (uma checagem rápida de sim/não feita por um modelo) ou agent (uma checagem experimental por subagente). O timeout padrão é de 600 segundos para handlers command, HTTP e de ferramenta MCP, 30 segundos para hooks de prompt e 60 para hooks de agente.

Todo hook recebe JSON no stdin. Os campos comuns são session_id, transcript_path, cwd, permission_mode e hook_event_name. Eventos de ferramenta adicionam tool_name, tool_input e tool_use_id. Seu script lê esse JSON, geralmente com jq, e decide o que fazer. A variável de ambiente CLAUDE_PROJECT_DIR aponta para a raiz do projeto, o que ajuda a referenciar scripts guardados no repositório.

Você pode digitar /hooks dentro do Claude Code para ver o que está carregado e de qual arquivo veio cada hook. Essa tela é somente leitura; para mudar hooks, você edita o JSON.

Matchers: quais chamadas de ferramenta um hook vê

O matcher decide quais chamadas disparam um hook. Em eventos de ferramenta, ele compara com o nome da ferramenta:

MatcherSignificado
"*", "" ou sem matcherToda chamada
"Bash"Exatamente a ferramenta Bash
"Edit|Write"Edit ou Write (uma lista)
"mcp__memory__.*"Toda ferramenta do servidor MCP memory
"mcp__.*__write.*"Qualquer ferramenta MCP cujo nome começa com write

A regra é simples. Se o matcher tem só letras, dígitos, _, -, espaços, vírgulas ou |, ele é um nome exato ou uma lista de nomes. Qualquer outro caractere o transforma numa expressão regular JavaScript. Ferramentas MCP se chamam mcp__<server>__<tool>.

Alguns eventos ignoram o matcher, como UserPromptSubmit e Stop, porque não há o que comparar. Outros comparam algo que não é o nome de uma ferramenta. SessionStart compara como a sessão começou (startup, resume, clear, compact), e Notification compara o tipo da notificação, como permission_prompt ou idle_prompt.

Eventos de ferramenta também aceitam um campo if com a sintaxe das regras de permissão, então um hook de Bash pode rodar só para, digamos, "Bash(git *)". A documentação descreve isso como um filtro de melhor esforço, então mantenha a checagem real no seu script.

Exit codes e saída JSON

Um hook de comando responde ao Claude Code de duas formas: o exit code e um JSON opcional no stdout.

Exit codeO que acontece
0Sucesso. O stdout é interpretado como JSON se for um único objeto JSON. Em UserPromptSubmit e SessionStart, texto puro no stdout é adicionado ao contexto do Claude.
2Erro bloqueante. O stderr é o motivo. Em PreToolUse a chamada é cancelada, em UserPromptSubmit o prompt é rejeitado, em Stop o Claude continua.
Qualquer outro, incluindo 1Erro não bloqueante. O Claude Code registra e segue em frente.

A armadilha aqui é o exit code 1. Muitos scripts falham com 1 por padrão, e isso não bloqueia nada. Se você quer dizer “pare”, saia com 2.

Para um controle mais fino, saia com 0 e imprima JSON. Um hook PreToolUse pode permitir, negar ou perguntar:

{
  "hookSpecificOutput": {
    "hookEventName": "PreToolUse",
    "permissionDecision": "deny",
    "permissionDecisionReason": "Use rg instead of grep for better performance"
  }
}

permissionDecision aceita allow, deny, ask ou defer. Um hook também pode reescrever a chamada com updatedInput ou passar texto extra ao Claude com additionalContext. Campos que funcionam em qualquer evento incluem continue: false (para o Claude por completo, com um stopReason) e systemMessage (um aviso mostrado a você).

Quando vários hooks batem, eles rodam em paralelo, e a resposta mais restritiva vence: deny ganha de defer, defer ganha de ask, ask ganha de allow. Um allow vindo de um hook não sobrepõe uma regra deny nas suas permissões.

Exemplo 1: formatar arquivos depois de cada edição

Este é o hook com que a maioria começa. Ele roda o Prettier em todo arquivo que o Claude escreve ou edita:

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "jq -r '.tool_input.file_path' | xargs npx prettier --write"
          }
        ]
      }
    ]
  }
}

Troque por ruff format, gofmt -w ou rustfmt para outras linguagens. Como o formatador roda depois da edição, o Claude vê o arquivo formatado na próxima leitura, e você para de ver diffs só de estilo na revisão.

Exemplo 2: bloquear comandos perigosos e arquivos protegidos

Um hook PreToolUse é o lugar para limites rígidos. Este script, adaptado do guia oficial, bloqueia edições em arquivos sensíveis. Salve como .claude/hooks/protect-files.sh:

#!/bin/bash
INPUT=$(cat)
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')

PROTECTED_PATTERNS=(".env" "package-lock.json" ".git/")

for pattern in "${PROTECTED_PATTERNS[@]}"; do
  if [[ "$FILE_PATH" == *"$pattern"* ]]; then
    echo "Blocked: $FILE_PATH matches protected pattern '$pattern'" >&2
    exit 2
  fi
done

exit 0

Torne-o executável com chmod +x .claude/hooks/protect-files.sh e depois registre:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/protect-files.sh"
          }
        ]
      }
    ]
  }
}

O mesmo padrão funciona para comandos de shell. Use o matcher Bash, leia .tool_input.command e saia com 2 quando ele contiver algo que você nunca quer, como rm -rf /, git push --force ou DROP TABLE. Lembre que uma checagem de string é fácil de contornar, então trate isso como cinto de segurança, não como cadeado.

Exemplo 3: receber uma notificação no desktop

Tarefas longas param e esperam por você mais do que você gostaria. Um hook Notification avisa na hora. No macOS:

{
  "hooks": {
    "Notification": [
      {
        "matcher": "",
        "hooks": [
          {
            "type": "command",
            "command": "osascript -e 'display notification \"Claude Code needs your attention\" with title \"Claude Code\"'"
          }
        ]
      }
    ]
  }
}

No Linux, use notify-send 'Claude Code' 'Claude Code needs your attention'. Defina o matcher como permission_prompt se só te interessam os pedidos de permissão, ou adicione um hook Stop com o mesmo comando para saber quando o Claude termina.

Mais padrões úteis

  • Reinjetar contexto depois da compactação. Um hook SessionStart com matcher compact pode dar echo num lembrete curto das suas regras. O stdout dele é adicionado ao novo contexto.
  • Auditar mudanças de configuração. Um hook ConfigChange pode acrescentar cada mudança de configuração a um arquivo de log.
  • Portão de qualidade no Stop. Um hook Stop pode rodar seus testes e sair com 2 trazendo as falhas, para o Claude continuar corrigindo. Verifique o campo stop_hook_active na entrada para evitar um loop infinito. O Claude Code também ignora um hook Stop depois de vários bloqueios seguidos.
  • Aprovar automaticamente um pedido seguro. Um hook PermissionRequest pode responder a um diálogo específico com {"behavior": "allow"} dentro do objeto de decisão. Mantenha o matcher bem restrito.

Notas de segurança antes de adicionar hooks

Hooks rodam como comandos de shell com as permissões do seu usuário, sem perguntar. Esse é o objetivo, e também o risco.

  • Leia os hooks antes de confiar num repositório. O .claude/settings.json de um projeto pode trazer hooks. Revise-os como você revisaria um script de postinstall.
  • Coloque suas variáveis entre aspas. A entrada do hook vem do modelo, e o modelo lê arquivos e páginas web que podem conter prompt injection. Use "$VAR", nunca dê eval na entrada e prefira jq a parsing feito à mão.
  • Use caminhos absolutos ou relativos ao projeto com $CLAUDE_PROJECT_DIR, para que um hook não dependa da pasta atual.
  • Mantenha os hooks rápidos. Um hook PreToolUse lento deixa toda chamada de ferramenta lenta. Defina um timeout.
  • Hooks não são um sandbox. Um hook de deny bloqueia chamadas mesmo com --dangerously-skip-permissions, o que é útil, mas um agente determinado ou confuso pode chegar ao mesmo resultado por outro caminho. Combine hooks com regras deny e isolamento, como explicado em segurança de agentes de código com IA e pular permissões com segurança.
  • Conheça os botões de desligar. "disableAllHooks": true desliga os hooks, e admins podem definir allowManagedHooksOnly nas configurações gerenciadas para bloquear hooks de usuário e de projeto.

Quando um hook se comporta mal, inicie o Claude Code com claude --debug, ou grave o log num arquivo com --debug-file /tmp/claude.log, para ver o matcher, o exit code e a saída de cada hook.

Como o AgentVera usa hooks

O AgentVera é um app desktop que roda Claude Code, Codex, Gemini CLI e outras CLIs de código lado a lado. Hooks são uma parte importante de como ele trabalha com o Claude Code.

  • Seus hooks continuam seus. O AgentVera passa os próprios hooks para cada processo do Claude Code com a flag --settings. O Claude Code os mescla com os hooks dos seus arquivos de configuração, e o seu settings.json não é editado.
  • O status do agente vem dos hooks. O status que cada agente mostra no app é lido dos eventos de hook da própria CLI, não adivinhado a partir do texto do terminal.
  • As ferramentas inteligentes são hooks. O recurso de ferramentas inteligentes adiciona quatro hooks aos agentes Claude Code locais: bloqueia leituras repetidas do mesmo arquivo, roda só os testes afetados por uma edição, verifica erros logo depois de uma edição e compartilha resultados entre agentes. Eles vêm ativados por padrão e você pode desativá-los em Settings → “Token saving”.
  • Nenhum script de notificação necessário. Notificações e aprovações reúnem numa só fila todos os agentes esperando aprovação, enviam notificações no desktop enquanto o app está em segundo plano e podem avisar seu celular pelo Telegram.

O Qwen Code também recebe os hooks do AgentVera, porque o sistema de hooks dele segue os eventos e o formato JSON do Claude Code.

Conclusão

Comece com um formatador PostToolUse e uma barreira PreToolUse, confira-os com /hooks e --debug e só adicione mais quando uma regra continuar sendo quebrada. Saia com 2 para bloquear, imprima JSON quando precisar de mais controle e trate cada hook como código que roda com as suas permissões.

Leia mais:

Para experimentar o AgentVera, comece pela página inicial.

Perguntas frequentes

O que são hooks do Claude Code?

Hooks são comandos de shell, chamadas HTTP, ferramentas MCP ou prompts que o Claude Code executa sozinho em pontos definidos de uma sessão, como antes de uma chamada de ferramenta, depois de editar um arquivo ou quando o Claude precisa da sua atenção. Você os define no settings.json.

Onde coloco os hooks do Claude Code?

Na chave hooks de um arquivo de configurações: ~/.claude/settings.json para todos os seus projetos, .claude/settings.json para um projeto compartilhado via git, ou .claude/settings.local.json para um projeto só na sua máquina. Plugins, skills e subagentes também podem definir hooks.

Como um hook bloqueia uma chamada de ferramenta?

Um hook PreToolUse sai com código 2 e escreve o motivo no stderr, ou sai com 0 e imprime JSON com permissionDecision igual a deny. O Claude Code cancela a chamada e mostra o motivo ao Claude. O exit code 1 não bloqueia.

Os hooks ainda rodam com --dangerously-skip-permissions?

Sim. Um hook que retorna deny bloqueia a chamada de ferramenta mesmo no modo bypassPermissions. Isso faz de um hook PreToolUse uma última barreira útil para comandos que você nunca quer, embora não seja um sandbox.

Como depuro um hook do Claude Code que não dispara?

Abra /hooks para ver quais hooks estão carregados e de onde vêm, confira se o matcher bate exatamente com o nome da ferramenta e inicie o Claude Code com --debug ou --debug-file para ver cada execução do hook, seu exit code e sua saída.

Reúna seus agentes em uma só mesa.

Baixe o AgentVera de graça; as CLIs que você já instalou estão prontas para usar.

Mais artigos