Hooks no Claude Code: guia prático
Downshift por Tiago Vilas Boas. Atualizado em
Este Downshift é uma ferramenta em Go para agentes de código com IA, não a biblioteca React downshift de autocomplete.
Resumo
Hooks no Claude Code executam código seu em pontos fixos da sessão. Um hook PreToolUse vê cada chamada de ferramenta antes de ela rodar e pode permitir, negar, perguntar ou reescrever a entrada com updatedInput. Por padrão, falha não bloqueia. Este guia cobre os 33 eventos, configuração, códigos de saída, matchers e três exemplos seguros, e mostra como o Downshift usa dois hooks PreToolUse em produção.
O que são hooks no Claude Code?
Hooks são código seu que o Claude Code executa em pontos fixos da sessão: antes de uma ferramenta rodar, depois dela, quando você envia um prompt, quando um subagente termina, entre outros. Hook não é dica para o modelo. É um handler que roda toda vez que o evento acontece, e por isso é o lugar das regras que você não quer que o modelo esqueça.
Na maioria dos casos o handler é um comando de shell. A referência também traz os tipos http, mcp_tool, prompt e agent. Um hook de comando lê um JSON no stdin e responde com um código de saída e, se quiser, um objeto JSON no stdout.
Quais eventos de hook o Claude Code tem?
A referência lista 33 eventos. Abaixo está a lista publicada, um por linha. PreToolUse, UserPromptExpansion e PreModelSwitch são os que a tabela marca como capazes de bloquear.
| Evento | Dispara quando |
|---|---|
SessionStart | Uma sessão começa ou é retomada |
Setup | O Claude Code inicia com --init-only, ou com --init ou --maintenance no modo -p |
UserPromptSubmit | Um prompt é enviado, antes de o Claude processá-lo |
UserPromptExpansion | Um comando digitado vira prompt; pode bloquear a expansão |
PreToolUse | Antes de uma chamada de ferramenta rodar; pode bloqueá-la |
PermissionRequest | Uma chamada de ferramenta precisa de decisão de permissão |
PermissionDenied | O modo auto nega uma chamada de ferramenta |
PostToolUse | Depois que uma chamada de ferramenta dá certo |
PostToolUseFailure | Depois que uma chamada de ferramenta falha |
PostToolBatch | Depois que um lote de chamadas paralelas termina, antes da próxima chamada ao modelo |
Notification | O Claude Code envia uma notificação |
MessageDisplay | O texto de uma resposta do assistente é exibido |
SubagentStart | Um subagente é criado |
SubagentStop | Um subagente termina |
TaskCreated | Uma tarefa está sendo criada via TaskCreate |
TaskCompleted | Uma tarefa está sendo marcada como concluída |
Stop | O Claude termina de responder |
StopFailure | O turno termina por erro de API |
TeammateIdle | Um colega de um time de agentes vai ficar ocioso |
InstructionsLoaded | Um CLAUDE.md ou .claude/rules/*.md é carregado |
ConfigChange | Um arquivo de configuração muda durante a sessão |
CwdChanged | O diretório de trabalho muda |
DirectoryAdded | Um diretório de trabalho é adicionado no meio da sessão |
FileChanged | Um arquivo observado muda no disco |
WorktreeCreate | Um worktree está sendo criado |
WorktreeRemove | Um worktree criado por um hook WorktreeCreate está sendo removido |
PreCompact | Antes da compactação do contexto |
PostCompact | Depois que a compactação do contexto termina |
PreModelSwitch | Antes de aplicar uma troca de modelo pedida; pode bloqueá-la |
PostModelSwitch | Depois que o modelo da sessão muda |
Elicitation | Um servidor MCP pede uma entrada do usuário durante uma chamada |
ElicitationResult | Depois que o usuário responde a esse pedido do MCP |
SessionEnd | Uma sessão termina |
Verificado em 2026-10-24 na referência de hooks do Claude Code. Eventos, padrões e campos mudam entre versões; confira a sua.
Onde configurar hooks no Claude Code?
Os hooks ficam sob a chave hooks, e as entradas de cada nível se somam em vez de se substituírem.
~/.claude/settings.json: todos os projetos da sua máquina..claude/settings.json: um projeto, compartilhado pelo repositório..claude/settings.local.json: um projeto, fora do git.- Settings gerenciadas: valem para a organização, definidas por um admin.
hooks/hooks.jsonde um plugin: ativo enquanto o plugin estiver habilitado.- Frontmatter de skill e de subagente: ativo enquanto aquela skill ou subagente estiver em uso.
A estrutura tem três níveis: evento, grupo com matcher e handlers.
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{ "type": "command", "command": "~/.claude/hooks/check-bash.sh", "timeout": 10 }
]
}
]
}
}A página de settings do Claude Code explica a precedência das outras chaves, e o guia de hooks faz um passo a passo.
Como o hook conversa com o Claude Code: entrada, saída e códigos de saída
Todo hook de comando recebe no stdin um JSON com campos comuns como session_id, transcript_path, cwd, permission_mode e hook_event_name. Eventos de ferramenta somam tool_name, tool_input e tool_use_id. Dentro de um subagente chegam também agent_id e agent_type.
- Saída 0: sucesso. Se o stdout for um único objeto JSON, o Claude Code lê como decisão; se não, vira texto.
- Saída 2: erro que bloqueia, nos eventos que podem bloquear. O stderr vira o motivo. No
PostToolUseele só mostra o stderr, porque a ferramenta já rodou. - Qualquer outro código: erro que não bloqueia. A ação segue e aparece um aviso de erro do hook. Saída 1 não bloqueia; para política, use saída 2 ou um deny em JSON.
Deixe o stdout limpo. Um echo esquecido antes do JSON transforma a decisão em texto.
Como um hook PreToolUse permite, nega, pergunta ou reescreve uma chamada?
Este é o evento que mais importa. O hook PreToolUse responde com hookSpecificOutput contendo hookEventName: "PreToolUse", um permissionDecision e um permissionDecisionReason.
| permissionDecision | O que faz |
|---|---|
allow | Pula o pedido de permissão. Regras deny e ask das suas settings continuam valendo. |
deny | Impede a chamada; o motivo vai para o Claude. |
ask | Pede sua confirmação, com o motivo no prompt, até no modo auto. |
defer | Encerra o turno para retomar a ferramenta depois; só com -p, ignorado no modo interativo. |
Quando vários hooks discordam, a referência define a precedência deny > defer > ask > allow.
O quarto poder é reescrever. O updatedInput, dentro de hookSpecificOutput, troca o objeto de entrada inteiro da ferramenta antes de ela rodar. Combine com allow para aprovar direto, ou com ask para mostrar a entrada alterada ao usuário. Duas regras pegam muita gente: devolva todos os campos que não mudaram, e lembre que as regras de permissão são avaliadas sobre a entrada que o hook devolve, não sobre a que o Claude mandou.
Verificado em 2026-10-24 na referência de hooks do Claude Code. Eventos, padrões e campos mudam entre versões; confira a sua.
Como funcionam os matchers dos hooks?
"*",""ou sem matcher: casa com tudo.- Só letras, dígitos,
_,-, espaço, vírgula e barra vertical: nome exato, ou lista comoEdit|Write. - Qualquer outro caractere: expressão regular JavaScript sem âncora.
Edit.*também pegaNotebookEdit; use^Edit$para o nome inteiro.mcp__memory__.*pega todas as ferramentas de um servidor MCP.
Em eventos de ferramenta, o handler aceita um campo if na sintaxe das regras de permissão, como Bash(git *), para rodar só em algumas chamadas. A referência chama esse filtro de best-effort: ele reduz quando o hook roda, não é sua fronteira de segurança.
O que acontece quando um hook estoura o timeout ou falha?
Os padrões da referência: 600 segundos para handlers command, http e mcp_tool, 30 segundos para prompt, 60 para agent, e 30 segundos no UserPromptSubmit e nos eventos de troca de modelo. Defina seu próprio timeout, em segundos, em cada handler.
No PreToolUse, um hook que quebra ou estoura o tempo não bloqueia a chamada. Ela segue pelo fluxo normal de permissão, como se o hook não tivesse dito nada. Se você quer que a falha bloqueie, use "onFailure": "block"; a referência indica que existe a partir da v2.1.295. Já no PreModelSwitch, o timeout bloqueia a troca.
Esse padrão acerta na maioria dos casos: um bug no seu logger não deve parar a sessão. É também por isso que hook é guarda-corpo, não cadeado.
Exemplos de hooks no Claude Code: três hooks seguros e mínimos
Cada script precisa de jq e de chmod +x. Cada um faz uma coisa só e fica quieto no resto.
1. Bloquear um comando destrutivo
Nega rm -rf e deixa passar o resto do Bash. O filtro if evita que o script rode em comandos que não interessam.
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{ "type": "command", "if": "Bash(rm *)", "command": "~/.claude/hooks/block-rm-rf.sh" }
]
}
]
}
}#!/bin/bash
# block-rm-rf.sh: nega rm -rf, fica em silêncio no resto
cmd=$(jq -r '.tool_input.command // ""')
if printf '%s' "$cmd" | grep -Eq 'rm[[:space:]]+-[a-zA-Z]*r[a-zA-Z]*f|rm[[:space:]]+-[a-zA-Z]*f[a-zA-Z]*r'; then
jq -n '{hookSpecificOutput: {hookEventName: "PreToolUse",
permissionDecision: "deny",
permissionDecisionReason: "rm -rf bloqueado por um hook local"}}'
fi
exit 0Para uma regra dura, acrescente também um deny em permissions nas settings; a referência recomenda o sistema de permissões para isso.
2. Registrar chamadas sem o conteúdo
Um rastro de quais ferramentas rodaram e quando, sem argumentos, caminhos de arquivo ou saída. O async tira o log do caminho crítico.
{
"hooks": {
"PostToolUse": [
{
"matcher": "*",
"hooks": [
{ "type": "command", "command": "~/.claude/hooks/log-tool.sh", "async": true }
]
}
]
}
}#!/bin/bash
# log-tool.sh: só nome da ferramenta, sessão e hora; nunca tool_input nem saída
jq -c '{ts: (now | todate), session: .session_id, tool: .tool_name}' \
>> ~/.claude/tool-calls.jsonl3. Trocar o modelo de um subagente
Devolva o tool_input original mudando só o model. Use o nome da família; em 2026-10-06 um id completo de modelo foi recusado e o subagente nem subiu.
{
"hooks": {
"PreToolUse": [
{
"matcher": "Agent|Task",
"hooks": [
{ "type": "command", "command": "~/.claude/hooks/subagent-model.sh" }
]
}
]
}
}#!/bin/bash
# subagent-model.sh: devolve o tool_input inteiro, muda só o model
jq '{hookSpecificOutput: {hookEventName: "PreToolUse",
permissionDecision: "allow",
updatedInput: (.tool_input + {model: "haiku"})}}'Este responde sempre haiku: mostra o mecanismo, não é uma política. O guia de PreToolUse para o modelo do subagente (em inglês) traz os detalhes.
Hooks no Claude Code são seguros? Cuidados de segurança
- Hooks de comando rodam com as permissões do seu usuário, sem sandbox próprio. O que seu shell faz, o hook faz.
- Revise hook ou plugin de terceiros como código que você vai executar, porque é isso. Leia o script, o matcher e o que ele grava em disco ou manda pela rede.
- Hooks de projeto de um repositório clonado só rodam depois que você aceita o diálogo de confiança do workspace. Não aceite em repositório que você não leu.
- Admins podem ligar
allowManagedHooksOnlypara bloquear hooks de usuário, projeto e plugin; hooks HTTP podem ser limitados comallowedHttpHookUrls. - Prefira a forma exec (
args) para evitar que o shell interprete caracteres especiais. - Silêncio de hook nunca aprova chamada. Deixe allow e deny duros nas permissões; use hook para a lógica que regra não expressa.
Exemplo real: como o Downshift usa hooks PreToolUse
O Downshift é um binário Go open source que traz dois hooks PreToolUse para o Claude Code, os dois registrados pelo plugin.
O hook de subagente: matcher Agent|Task
Quando o Claude cria um subagente, o Downshift pontua o texto da tarefa localmente, sem chamar LLM, e escolhe um nível. Se está confiante, devolve updatedInput com os mesmos campos e só model e reasoning_effort alterados. Se está em dúvida, segura, e o subagente roda exatamente como o Claude pediu.
// tool_input que o Claude enviou
{ "description": "Rename variable", "prompt": "rename userId to accountId",
"subagent_type": "general-purpose", "model": "opus" }
// updatedInput que o Downshift devolve: mesmos campos, só o model mudou
{ "description": "Rename variable", "prompt": "rename userId to accountId",
"subagent_type": "general-purpose", "model": "haiku" }- Falha aberta. O stdin tem teto de 1 MiB e leitura com timeout de 2 segundos, o hook inteiro tem prazo de 1,5 segundo, e um panic deixa o subagente como estava.
- O texto da tarefa fica só na memória. É pontuado e descartado, nunca vai para o disco; a telemetria guarda contagens, hashes e tempos.
- Os outros campos não mudam.
prompt,descriptionesubagent_typevoltam como chegaram.
O Compactor: matcher Bash
Quando o agente roda um comando de teste, build ou status que está na allowlist (go test, git status, pytest, vitest, jest, tsc, cargo test e outros), o hook reescreve o command para passar pelo downshift compact. Execução verde volta como uma linha de resumo, falha volta inteira, e o código de saída real é mantido.
# o que o agente pediu
go test ./...
# o que roda depois do hook PreToolUse de Bash (allowlist, sem metacaracteres)
downshift compact -- go test ./...Ele só reescreve comandos sem metacaracteres de shell, e uma regra deny ou ask das suas settings que case com o comando impede a reescrita: o hook nunca passa um comando por cima das suas regras. "exec": false ou DOWNSHIFT_EXEC=0 desliga.
Perguntas frequentes
O que é um hook no Claude Code?
Um handler, quase sempre um comando de shell, que o Claude Code executa num ponto fixo da sessão, como antes de uma chamada de ferramenta. O hook de comando lê um JSON no stdin e responde com um código de saída e, se quiser, um objeto JSON no stdout.
Um hook PreToolUse consegue mudar a entrada de uma ferramenta?
Consegue. Devolva updatedInput dentro de hookSpecificOutput junto com permissionDecision allow ou ask. Ele substitui o objeto de entrada inteiro, então devolva também os campos que não mudaram.
Um hook com bug trava o Claude Code?
No PreToolUse, por padrão, não. Saída 2 ou uma decisão deny bloqueiam. Crash, timeout ou qualquer outro código deixam a chamada seguir pelo fluxo normal de permissão, a menos que o hook use onFailure block.
O allow de um hook passa por cima das minhas regras deny?
Não. Segundo a referência de hooks, as regras deny e ask das suas settings são avaliadas de qualquer jeito, e sobre a entrada que o hook devolve.
O Downshift lê ou guarda meus prompts?
Ele lê o texto da tarefa do subagente para pontuar, só na memória, e nunca grava. Muda apenas model e reasoning_effort e, em qualquer erro, deixa o subagente como estava.
Coloque um hook para trabalhar em cada subagente
Instale o Downshift, veja cada alteração antes com downshift setup --dry-run, e deixe os dois hooks PreToolUse rotearem subagentes e compactarem a saída de teste. downshift setup --undo restaura os backups.
curl -fsSL https://raw.githubusercontent.com/tiagovilasboas/downshift/main/install.sh | sh && downshift setupPlugin do Claude Code
/plugin install downshift --marketplace tiagovilasboas/downshiftGuias relacionados
- Qual modelo cada subagente usa no Claude Code
- Saída longa de teste e build no Claude Code: por que gasta contexto e como cortar
- Como economizar tokens no Claude Code, sem mito
- Instalar o Downshift: guia de instalação
- Rewriting a subagent's model with a PreToolUse hookEnglish
- Claude Code hooks: a practical guideEnglish
Fontes
- Anthropic docsverificado
code.claude.com/docs/en/hooks
lista de eventos, campos de entrada, códigos de saída, decisões do PreToolUse, updatedInput, matchers, timeouts, segurança (consultado em 2026-10-24) - Anthropic docsverificado
code.claude.com/docs/en/hooks-guide
passo a passo de configuração (link, sem citação) - Anthropic docsverificado
code.claude.com/docs/en/settings
arquivos e escopos de settings (link, sem citação) - Downshift repoverificado
docs/evidence/four-arm-mechanical-2026-10-21.md
números da bateria de quatro braços, 2026-10-21 - Downshift repoverificado
hooks/hooks.json
matchers que o Downshift registra no Claude Code