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
.envou rodarrm -rfna 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:
| Evento | Quando dispara | Pode bloquear? |
|---|---|---|
PreToolUse | Antes de uma chamada de ferramenta rodar | Sim, bloqueia a chamada |
PostToolUse | Depois que uma chamada de ferramenta dá certo | Não, a ferramenta já rodou; o stderr vai para o Claude |
UserPromptSubmit | Quando você envia um prompt | Sim, o prompt nunca chega ao Claude |
Notification | Quando o Claude Code envia uma notificação, como um pedido de permissão ou espera ociosa | Não |
Stop | Quando o Claude termina a resposta | Sim, o Claude continua trabalhando |
SubagentStart / SubagentStop | Quando um subagente inicia ou termina | Só SubagentStop |
SessionStart | Na inicialização, ao retomar, em /clear ou na compactação | Não, mas o stdout vira contexto |
PreCompact | Antes de o contexto ser compactado | Sim, bloqueia a compactação |
PermissionRequest | Quando um diálogo de permissão está prestes a aparecer | Só via JSON, não com exit 2 |
SessionEnd | Quando a sessão termina | Nã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:
| Arquivo | Escopo | Compartilhado via git? |
|---|---|---|
~/.claude/settings.json | Todos os seus projetos | Não |
.claude/settings.json | Um projeto | Sim |
.claude/settings.local.json | Um projeto, só você | Não (no gitignore) |
| Configurações de política gerenciada | Organização inteira | Definidas 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:
| Matcher | Significado |
|---|---|
"*", "" ou sem matcher | Toda 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 code | O que acontece |
|---|---|
0 | Sucesso. O stdout é interpretado como JSON se for um único objeto JSON. Em UserPromptSubmit e SessionStart, texto puro no stdout é adicionado ao contexto do Claude. |
2 | Erro bloqueante. O stderr é o motivo. Em PreToolUse a chamada é cancelada, em UserPromptSubmit o prompt é rejeitado, em Stop o Claude continua. |
Qualquer outro, incluindo 1 | Erro 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
SessionStartcom matchercompactpode darechonum lembrete curto das suas regras. O stdout dele é adicionado ao novo contexto. - Auditar mudanças de configuração. Um hook
ConfigChangepode acrescentar cada mudança de configuração a um arquivo de log. - Portão de qualidade no Stop. Um hook
Stoppode rodar seus testes e sair com 2 trazendo as falhas, para o Claude continuar corrigindo. Verifique o campostop_hook_activena 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
PermissionRequestpode 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.jsonde um projeto pode trazer hooks. Revise-os como você revisaria um script depostinstall. - 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 prefirajqa 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
PreToolUselento deixa toda chamada de ferramenta lenta. Defina umtimeout. - 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": truedesliga os hooks, e admins podem definirallowManagedHooksOnlynas 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 seusettings.jsonnã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:
- Hooks do Claude Code (glossário)
- Ferramentas inteligentes
- Notificações e aprovações
- Agente Claude Code
Para experimentar o AgentVera, comece pela página inicial.