Hooks do Claude Code explicados: a camada determinística ao redor do seu agente
O que são os hooks do Claude Code? Hooks são comandos shell definidos pelo usuário (além de endpoints HTTP, ferramentas MCP e prompts de modelo) que o Claude Code executa automaticamente em pontos fixos do seu ciclo de vida: antes de uma chamada de ferramenta, depois de uma edição, no início da sessão, quando o Claude termina de responder.1 Enquanto o CLAUDE.md dá ao modelo instruções que ele provavelmente vai seguir, os hooks executam com ou sem a cooperação do modelo. Digite /hooks dentro de qualquer sessão para ver todos os eventos do ciclo de vida e o que está conectado a cada um.
{.answer-block}
A maioria dos desenvolvedores usa o Claude Code com duas camadas de controle: permissões limitando o que o agente pode fazer, e o CLAUDE.md descrevendo o que ele deveria fazer. Hooks são a terceira camada, e a única que garante alguma coisa. A seguir: o modelo mental, todos os eventos do ciclo de vida na documentação atual, o contrato exato de entrada/saída, a configuração, cinco padrões funcionais e um framework de decisão. Cada detalhe da API foi verificado contra a referência oficial de hooks e o guia em 1º de julho de 2026 — este sistema evolui rápido, então onde este post e a referência divergirem, a referência vence. (Novo no Claude Code? Comece pela configuração em 5 minutos ou pela trilha Novo no Claude Code.)
TL;DR: Hooks recebem JSON no stdin e respondem com exit codes ou JSON no stdout. Exit 0 permite, exit 2 bloqueia (nos eventos que podem bloquear), e exit 1 — o código de falha convencional do Unix — não bloqueia nada, o que é a maior armadilha dos hooks.2 Configure-os no settings.json sob nomes de evento como PreToolUse e Stop, filtrados por matchers. Use hooks para tudo que precisa acontecer sempre; use o CLAUDE.md para tudo que o modelo apenas deveria saber.
O modelo mental: garantias ao redor de um núcleo não determinístico
Um agente de código é um sistema probabilístico. Peça para ele rodar o Prettier depois de cada edição e ele vai rodar — na maioria das vezes. Ele pode pular a etapa quando a mudança parece trivial, quando o contexto fica longo demais ou quando a sua formulação soa diferente. CLAUDE.md, skills e prompts são todos sugestões: de alta qualidade, geralmente seguidas, nunca garantidas.
Hooks são a casca determinística ao redor desse núcleo. A definição oficial: “comandos shell definidos pelo usuário, endpoints HTTP ou prompts de LLM que executam automaticamente em pontos específicos do ciclo de vida do Claude Code”, fornecendo “controle determinístico sobre o comportamento do Claude Code, garantindo que certas ações sempre aconteçam em vez de depender da escolha do LLM de executá-las”.3 O formatador dispara em cada edição. O guarda de comandos avalia cada chamada de Bash. O gate de conclusão verifica cada finalização.
A aplicação é real, não cosmética: hooks de PreToolUse disparam antes de qualquer verificação de modo de permissão, então um hook que retorna permissionDecision: "deny" bloqueia a ferramenta mesmo no modo bypassPermissions ou sob --dangerously-skip-permissions. O inverso não vale — um hook que retorna "allow" não pode afrouxar regras de negação das configurações. Hooks podem apertar a política além do que as permissões permitem, nunca enfraquecê-la.4
O ciclo de vida: todos os eventos de hook
Em 1º de julho de 2026, a referência documenta 30 eventos de hook.1 Eles se dividem em três cadências: uma vez por sessão (SessionStart, SessionEnd), uma vez por turno (UserPromptSubmit, Stop, StopFailure) e em cada chamada de ferramenta dentro do loop agêntico (PreToolUse, PostToolUse). Os demais disparam em condições específicas — mudanças de configuração, compactação, subagentes, interações MCP.
| Evento | Dispara | Um uso real |
|---|---|---|
SessionStart |
A sessão começa ou é retomada | Injetar a branch do git e issues abertas como contexto |
Setup |
--init-only, ou --init/--maintenance no modo -p |
Instalar dependências no CI antes de o agente rodar |
UserPromptSubmit |
Você envia um prompt, antes de o Claude processá-lo | Anexar a data atual; rejeitar prompts contendo segredos |
UserPromptExpansion |
Um comando digitado se expande em um prompt | Auditar ou vetar expansões de skills/comandos |
PreToolUse |
Antes de uma chamada de ferramenta executar | Bloquear comandos shell destrutivos |
PermissionRequest |
Um diálogo de permissão aparece | Aprovar automaticamente comandos confiáveis para você não ser interrompido |
PermissionDenied |
O classificador do modo automático nega uma chamada de ferramenta | Retornar retry: true para o modelo poder tentar de novo |
PostToolUse |
Depois de uma chamada de ferramenta ter sucesso | Formatar automaticamente cada arquivo editado |
PostToolUseFailure |
Depois de uma chamada de ferramenta falhar | Registrar comandos que falham para triagem |
PostToolBatch |
Depois de um lote de chamadas de ferramenta paralelas, antes da próxima chamada ao modelo | Criar um checkpoint ou interromper o loop agêntico |
Notification |
O Claude Code envia uma notificação | Alerta no desktop quando o Claude precisa de input |
MessageDisplay |
Enquanto o texto da mensagem do assistente é exibido | Censurar conteúdo na tela (apenas exibição; transcript inalterado) |
SubagentStart |
Um subagente é criado | Injetar contexto específico do tipo de agente |
SubagentStop |
Um subagente termina | Validar a saída do subagente antes de ela retornar |
TaskCreated |
Uma tarefa é criada via TaskCreate |
Impor regras de nomenclatura ou escopo de tarefas |
TaskCompleted |
Uma tarefa é marcada como concluída | Verificar critérios de aceitação antes que a conclusão seja efetivada |
Stop |
O Claude termina de responder | Gate de conclusão: bloquear a finalização até os testes passarem |
StopFailure |
O turno termina por causa de um erro de API | Alertar sobre rate_limit ou billing_error (apenas log; saída ignorada) |
TeammateIdle |
Um colega de um time de agentes está prestes a ficar ocioso | Manter os colegas trabalhando em uma fila de tarefas |
InstructionsLoaded |
Um arquivo CLAUDE.md ou .claude/rules/*.md é carregado no contexto |
Registrar quais instruções entraram na sessão |
ConfigChange |
Um arquivo de configuração muda no meio da sessão | Bloquear edições não autorizadas nas configurações |
CwdChanged |
O diretório de trabalho muda | Recarregar ambientes no estilo direnv |
FileChanged |
Um arquivo monitorado muda no disco | Atualizar variáveis de ambiente quando o .env muda |
WorktreeCreate |
Um worktree é criado via --worktree ou isolation: "worktree" |
Substituir o provisionamento padrão de worktree do git |
WorktreeRemove |
Um worktree é removido | Limpeza personalizada na saída da sessão ou do subagente |
PreCompact |
Antes da compactação de contexto | Salvar estado que você não pode se dar ao luxo de perder |
PostCompact |
Depois que a compactação termina | Reinjetar contexto crítico |
Elicitation |
Um servidor MCP solicita input do usuário | Preencher formulários automaticamente em execuções headless |
ElicitationResult |
Depois que você responde a uma elicitação MCP | Validar ou substituir a resposta antes de ela retornar |
SessionEnd |
A sessão termina | Arquivar logs, desmontar recursos |
Você não vai precisar da maioria deles. Quase toda configuração de produção é construída a partir de cinco: PreToolUse, PostToolUse, UserPromptSubmit, SessionStart e Stop. Os demais existem para o dia em que você precisar deles.
O contrato: JSON na entrada, exit codes ou JSON na saída
Hooks de comando recebem JSON no stdin e respondem por meio de exit codes, stdout e stderr. (Hooks HTTP recebem o mesmo JSON como corpo de um POST e respondem pelo corpo da resposta.)2
Todo evento entrega um envelope comum — session_id, transcript_path, cwd e hook_event_name, com permission_mode na maioria dos eventos — além de campos específicos do evento. Um hook de PreToolUse para um comando Bash recebe:
{
"session_id": "abc123",
"transcript_path": "/home/user/.claude/projects/.../transcript.jsonl",
"cwd": "/home/user/my-project",
"permission_mode": "default",
"hook_event_name": "PreToolUse",
"tool_name": "Bash",
"tool_input": { "command": "npm test" }
}
Outros eventos trocam a parte final: UserPromptSubmit carrega prompt, SessionStart carrega source (startup/resume/clear/compact), Stop carrega stop_hook_active e last_assistant_message. Hooks disparados dentro de subagentes recebem adicionalmente agent_id e agent_type.2
Exit codes
Três desfechos:2
- Exit 0 — sucesso. O Claude Code analisa o stdout em busca de campos de saída JSON. Para a maioria dos eventos, o stdout vai apenas para o log de debug; para
UserPromptSubmit,UserPromptExpansioneSessionStart, o stdout simples é adicionado como contexto que o Claude pode ver. - Exit 2 — erro bloqueante. O stdout (incluindo qualquer JSON) é ignorado; o stderr é devolvido ao Claude como mensagem de erro. O que “bloquear” significa depende do evento.
- Qualquer outro exit code — erro não bloqueante. O transcript mostra um aviso
<hook name> hook error, e a execução continua.
Essa última linha merece negrito: exit 1 não bloqueia nada. A documentação alerta sobre isso diretamente — o Claude Code trata exit 1 como um erro não bloqueante e prossegue, mesmo 1 sendo o código de falha convencional do Unix. Hooks de política precisam fazer exit 2.2
O que exit 2 faz, por evento:2
| Evento | Efeito do exit 2 |
|---|---|
PreToolUse |
Bloqueia a chamada de ferramenta |
PermissionRequest |
Nega a permissão |
UserPromptSubmit |
Bloqueia o processamento e apaga o prompt |
UserPromptExpansion |
Bloqueia a expansão |
Stop / SubagentStop |
Impede a parada; a conversa continua |
TeammateIdle |
Impede o colega de ficar ocioso |
TaskCreated / TaskCompleted |
Reverte a criação / impede a conclusão |
ConfigChange |
Bloqueia a mudança de configuração (exceto policy_settings) |
PreCompact |
Bloqueia a compactação |
PostToolBatch |
Interrompe o loop agêntico antes da próxima chamada ao modelo |
Elicitation / ElicitationResult |
Nega a elicitação / transforma a resposta em uma recusa |
WorktreeCreate |
Qualquer exit diferente de zero aborta a criação do worktree |
Todo o resto não pode bloquear. PostToolUse e PostToolUseFailure mostram o stderr ao Claude (a ferramenta já rodou); SessionStart, Notification, SessionEnd, CwdChanged, FileChanged, PostCompact, SubagentStart e Setup mostram o stderr apenas ao usuário; StopFailure, InstructionsLoaded, MessageDisplay e PermissionDenied ignoram o exit code — para PermissionDenied, a única alavanca é o JSON retry: true.2
Saída JSON
Para um controle mais fino do que bloquear-ou-silenciar, faça exit 0 e imprima um objeto JSON no stdout. Uma regra de antemão: exit codes ou JSON, nunca os dois — o JSON só é processado no exit 0, e o exit 2 o descarta.5
Campos universais funcionam em todos os eventos: continue: false para o Claude completamente (com stopReason mostrado ao usuário), suppressOutput esconde o stdout do transcript, systemMessage mostra um aviso ao usuário, e terminalSequence emite uma sequência de escape de terminal da lista de permitidas (notificação no desktop, título da janela, campainha). Os campos de decisão são por evento:5
| Eventos | Padrão de decisão | Campos principais |
|---|---|---|
UserPromptSubmit, UserPromptExpansion, PostToolUse, PostToolUseFailure, PostToolBatch, Stop, SubagentStop, ConfigChange, PreCompact |
decision no nível superior |
decision: "block" + reason (mostrado ao Claude). Omita decision para permitir |
PreToolUse |
hookSpecificOutput |
permissionDecision: "allow" | "deny" | "ask" | "defer", mais permissionDecisionReason e updatedInput para reescrever os argumentos da ferramenta antes da execução |
PermissionRequest |
hookSpecificOutput |
decision.behavior: "allow" | "deny", decision.updatedInput opcional |
PermissionDenied |
hookSpecificOutput |
retry: true diz ao modelo que ele pode tentar de novo |
PostToolUse |
hookSpecificOutput |
updatedToolOutput substitui o resultado da ferramenta |
Stop / SubagentStop |
hookSpecificOutput |
additionalContext: feedback sem erro que continua a conversa sem contar como erro de hook |
SessionStart, Setup, SubagentStart |
Apenas contexto | additionalContext, mais os campos exclusivos de SessionStart initialUserMessage, sessionTitle, watchPaths, reloadSkills. Sem bloqueio |
MessageDisplay |
hookSpecificOutput |
displayContent substitui apenas o texto na tela |
Elicitation / ElicitationResult |
hookSpecificOutput |
action: "accept" | "decline" | "cancel", mais content |
WorktreeRemove, Notification, SessionEnd, PostCompact, InstructionsLoaded, StopFailure, CwdChanged, FileChanged |
Nenhum | Apenas efeitos colaterais |
Dois detalhes que pegam as pessoas. Primeiro, PreToolUse é a exceção ao padrão de decision no nível superior: historicamente ele usava decision/reason no nível superior, mas esses campos estão deprecados para este evento ("approve"/"block" mapeiam para "allow"/"deny"); use hookSpecificOutput.permissionDecision.5 Segundo, quando vários hooks de PreToolUse discordam, a precedência é deny > defer > ask > allow.5
Configuração: settings.json, matchers, escopo
A configuração de hooks aninha três níveis: escolha um evento, adicione um grupo de matcher para filtrar quando ele dispara e defina um ou mais handlers de hook para executar.6
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{ "type": "command", "command": "/path/to/lint-check.sh" }
]
}
]
}
}
Onde você coloca isso determina o escopo: ~/.claude/settings.json se aplica a todos os seus projetos, .claude/settings.json tem escopo de projeto e pode ser commitado, .claude/settings.local.json tem escopo de projeto e fica no gitignore, e a precedência padrão de configurações se aplica — política gerenciada sobre local, sobre projeto, sobre usuário.9 Hooks também podem vir em plugins (hooks/hooks.json) e no frontmatter de skills ou agentes, e administradores corporativos podem impor hooks gerenciados que os usuários não conseguem sobrescrever.6
Matchers são avaliados pelos seus caracteres: "*", "" ou um matcher omitido corresponde a tudo; um valor contendo apenas letras, dígitos, _, -, espaços, vírgulas e | é uma string exata ou uma lista (Bash, Edit|Write); qualquer outra coisa vira uma regex JavaScript sem âncoras, então Edit.* corresponde tanto a Edit quanto a NotebookEdit — ancore com ^Edit$ quando quiser exatamente uma ferramenta. Matchers diferenciam maiúsculas de minúsculas, e cada evento faz a correspondência no seu próprio campo: nome da ferramenta para eventos de ferramenta, source para SessionStart, tipo de agente para SubagentStart, tipo de notificação para Notification.6 Para uma filtragem mais precisa em eventos de ferramenta, o campo if por handler aceita uma regra de permissão como "Bash(git *)" — mas ele é best-effort (falha aberto em comandos que não consegue analisar), então use regras de permissão, não if, para garantias rígidas.6
Handlers vêm em cinco tipos: command (shell), http (endpoint POST), mcp_tool, prompt (avaliação de modelo em turno único) e agent (um subagente com acesso a Read/Grep/Glob; experimental). Timeouts padrão: 600 segundos para command/http/mcp_tool (reduzido para 30 no UserPromptSubmit e 10 no MessageDisplay), 30 para prompt, 60 para agent — sobrescreva por hook com timeout.6 Todos os hooks correspondentes rodam em paralelo, com handlers idênticos deduplicados, e $CLAUDE_PROJECT_DIR aponta os scripts para a raiz do seu projeto.
Verifique com /hooks: um navegador somente leitura mostrando cada evento, seus hooks configurados e de qual arquivo de configurações cada um veio. Para mudar qualquer coisa, edite o JSON (ou peça ao Claude). Para desligar tudo temporariamente, defina "disableAllHooks": true.6
Cinco padrões
Genéricos e mínimos. O tutorial de hooks constrói versões de produção mais completas de vários deles, e Hooks para desenvolvimento Apple os aplica ao toolchain do iOS.
1. Formatação automática depois de edições (PostToolUse)
Direto do guia oficial — todo arquivo que o Claude toca é formatado, sem exceções:3
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{ "type": "command", "command": "jq -r '.tool_input.file_path' | xargs npx prettier --write" }
]
}
]
}
}
Troque o comando por ruff format, gofmt ou swiftformat conforme a sua stack exigir.
2. Bloquear comandos perigosos (PreToolUse, exit 2)
#!/bin/bash
# .claude/hooks/guard-bash.sh — register on PreToolUse, matcher "Bash"
command=$(jq -r '.tool_input.command // empty')
case "$command" in
*"rm -rf"* | *"git push --force"* | *"DROP TABLE"*)
echo "Blocked: matches a destructive pattern. Propose a safer alternative." >&2
exit 2 ;;
esac
exit 0
Exit 2 bloqueia a chamada e devolve o stderr ao Claude, que ajusta o rumo em vez de tentar de novo às cegas. O equivalente em JSON — permissionDecision: "deny" com um motivo — faz o mesmo, com espaço para evoluir para "ask" (escalar para o humano) ou updatedInput (reescrever o comando).5
3. Injetar contexto no início da sessão (SessionStart)
O stdout simples de um hook de SessionStart vira contexto que o Claude pode ver — sem precisar de JSON:1
#!/bin/bash
# .claude/hooks/session-context.sh — register on SessionStart
echo "Current branch: $(git branch --show-current)"
echo "Recent commits:"
git log --oneline -5
echo "Uncommitted files: $(git status --porcelain | wc -l | tr -d ' ')"
exit 0
Use isso para estado dinâmico. Convenções estáticas pertencem ao CLAUDE.md, que a própria documentação recomenda para contexto que não exige um script.1
4. Um gate de conclusão no Stop
O Stop dispara quando o Claude termina de responder. Bloqueá-lo força o agente a continuar trabalhando até uma condição ser satisfeita:
#!/bin/bash
# .claude/hooks/stop-gate.sh — register on Stop
input=$(cat)
if [ "$(echo "$input" | jq -r '.stop_hook_active')" = "true" ]; then
exit 0 # already continuing because of this hook; don't loop forever
fi
if ! npm test --silent > /tmp/stop-gate.log 2>&1; then
jq -n '{decision: "block", reason: "Tests are failing. Fix them before finishing. Log: /tmp/stop-gate.log"}'
fi
exit 0
A verificação de stop_hook_active importa: o Claude Code impõe um limite rígido de 8 bloqueios consecutivos para um hook de Stop, e um gate que nunca verifica se já disparou uma continuação vai queimar todos eles de uma vez.7 Para um direcionamento mais suave, retorne hookSpecificOutput.additionalContext em vez de decision: "block" — a mesma continuação, mas como feedback rotulado em vez de um erro de hook. E para condições pontuais, o comando embutido /goal é um hook de Stop baseado em prompt, com escopo de sessão e zero configuração.1
5. O dispatcher: um ponto de entrada, muitos hooks pequenos
Registrar dez hooks significa dez entradas no settings.json que se desalinham entre máquinas e projetos. A alternativa: registrar um dispatcher por evento e rotear por convenção.
#!/bin/bash
# .claude/hooks/dispatch.sh — register once per event you care about
input=$(cat)
event=$(echo "$input" | jq -r '.hook_event_name')
dir="$CLAUDE_PROJECT_DIR/.claude/hooks/$event"
[ -d "$dir" ] || exit 0
for hook in "$dir"/*.sh; do
[ -x "$hook" ] || continue
echo "$input" | "$hook" || exit $?
done
exit 0
Adicionar um guarda agora é um chmod +x em um arquivo novo em .claude/hooks/PreToolUse/ — o settings.json nunca muda, cada script permanece pequeno o suficiente para ser testado isoladamente, e o primeiro exit 2 se propaga. Uma ressalva: o dispatcher serializa o que o Claude Code executaria em paralelo, e ele serve melhor para hooks de exit code — um hook que emite JSON deve permanecer independente, já que o stdout precisa conter exatamente um objeto JSON.5
Hook vs CLAUDE.md vs skill vs memória
Quatro mecanismos, quatro funções:
| Mecanismo | Função | Regra para escolher |
|---|---|---|
| Hook | Imposição | Se pular precisa ser impossível — formatação, segurança, gates — é um hook |
| CLAUDE.md | Orientação | Se é uma convenção que o modelo deve saber em toda sessão — stack, estilo, comandos — é CLAUDE.md |
| Skill | Capacidade | Se é um procedimento com instruções e scripts próprios, invocado quando relevante, é uma skill |
| Memória | Lembrança | Se é um fato aprendido em uma sessão de que as sessões futuras precisam, é memória |
O modo de falha corre nas duas direções. Codificar convenções como hooks te dá scripts frágeis impondo coisas que uma frase de orientação resolve bem. Codificar política como prosa no CLAUDE.md te dá um agente que faz force-push na main justamente no dia em que importa. O teste: qual é o custo quando o modelo ignora isso uma vez? Aborrecimento → CLAUDE.md. Incidente → hook.
O que os hooks não podem fazer
Limites honestos, todos da documentação oficial:7
- Hooks não podem chamar ferramentas nem slash commands. Hooks de comando falam stdout, stderr e exit codes — nada mais. O contexto retornado via
additionalContexté injetado como texto simples. PostToolUsenão pode desfazer. A ferramenta já rodou. A prevenção mora noPreToolUse.Stopdispara no fim de toda resposta, não apenas em “tarefa concluída”, e nunca em interrupções do usuário (erros de API disparamStopFailureno lugar). A lógica do gate precisa tolerar paradas no meio da tarefa.PermissionRequestnão dispara no modo headless (-p). UsePreToolUsepara decisões de permissão automatizadas.PreToolUsenão vê arquivos referenciados com@. Arquivos puxados via@no seu prompt não envolvem chamada de ferramenta; use regras de negação deReadpara proteger caminhos por essa rota.1updatedInputem paralelo é não determinístico. Quando vários hooks de PreToolUse reescrevem os argumentos da mesma ferramenta, o último a terminar vence. Deixe um único hook ser dono de cada reescrita.- Timeouts cancelam o hook. 600 segundos por padrão para hooks de comando (30 para
UserPromptSubmit, 10 paraMessageDisplay); um gate lento que estoura o tempo é um gate que não rodou. - A saída é limitada a 10.000 caracteres — o excedente é gravado em um arquivo e substituído por uma prévia.
- Hooks rodam com todas as permissões do seu usuário. O alerta da própria referência: eles “podem modificar, excluir ou acessar quaisquer arquivos que a sua conta de usuário pode acessar. Revise e teste todos os comandos de hook antes de adicioná-los à sua configuração.”8 Coloque aspas nas suas variáveis, use caminhos absolutos, evite arquivos sensíveis.
- Um hook quebrado degrada todas as sessões até ser corrigido. Depure com a visualização do transcript (
Ctrl+O),claude --debug-file /tmp/claude.logou/debugno meio da sessão; uma pegadinha clássica é um perfil de shell que ecoa algo na inicialização e corrompe a saída JSON do seu hook.7
FAQ
O que são os hooks do Claude Code?
Hooks são comandos definidos pelo usuário — scripts shell, endpoints HTTP, ferramentas MCP ou prompts de modelo — que o Claude Code executa automaticamente em pontos específicos do ciclo de vida.3 Eles recebem o JSON do evento no stdin e respondem com exit codes ou JSON: bloqueiam uma chamada de ferramenta, injetam contexto, reescrevem argumentos, mantêm o agente trabalhando. Diferentemente das instruções do CLAUDE.md, eles executam todas as vezes, independentemente do comportamento do modelo.
Qual é a diferença entre hooks de PreToolUse e permissões?
Regras de permissão são declarativas: padrões estáticos de allow/deny/ask que o próprio Claude Code avalia. Hooks de PreToolUse são programáveis: o seu código inspeciona a entrada completa da ferramenta e decide. Hooks disparam antes das verificações de modo de permissão, então o "deny" de um hook vale mesmo no modo bypassPermissions — mas o "allow" de um hook não pode sobrescrever uma regra de negação das configurações.4 Use regras de permissão para tudo que um padrão consegue expressar; recorra a um hook quando a decisão precisa de lógica, estado externo ou reescrita de entrada.
Hooks funcionam no modo headless (-p)?
Sim — com uma exceção documentada: hooks de PermissionRequest não disparam no modo não interativo, então decisões de permissão automatizadas pertencem ao PreToolUse.7 O modo headless também desbloqueia uma opção que sessões interativas ignoram: permissionDecision: "defer", que pausa uma chamada de ferramenta para que um processo que encapsula a sessão (um app do Agent SDK, uma UI personalizada) possa coletar input e retomar a sessão depois.5
Por que meu hook roda mas não bloqueia nada?
Quase sempre é uma violação do contrato. Exit 1 não bloqueia — só exit 2 bloqueia, e apenas nos eventos que suportam bloqueio.2 Decisões em JSON só são analisadas no exit 0 — um script que imprime {"decision": "block"} e depois faz exit 2 tem seu JSON descartado. E matchers diferenciam maiúsculas de minúsculas — bash nunca corresponde a Bash. Confirme o registro com /hooks e depois teste passando um JSON de exemplo via pipe para o script e verificando echo $?.7
Fontes
Verificado contra a documentação oficial em 1º de julho de 2026. A API de hooks mudou substancialmente ao longo das versões v2.1.x do Claude Code (novos eventos, novos campos, semântica de matchers), então trate os detalhes sensíveis a versão como válidos “nesta data”.
Relacionado neste site: a seção de hooks do guia de Claude Code para a visão completa do sistema, incluindo hooks de prompt e de agente, o tutorial de hooks com cinco construções de produção e configurações completas, Hooks para desenvolvimento Apple com os padrões aplicados ao iOS, e o quickstart se você ainda não instalou o Claude Code.
-
Anthropic, “Hooks reference — Hook lifecycle and hook events.” code.claude.com/docs/en/hooks#hook-events ↩↩↩↩↩↩
-
Anthropic, “Hooks reference — Hook input and output; exit code output; exit code 2 behavior per event.” code.claude.com/docs/en/hooks#exit-code-output ↩↩↩↩↩↩↩↩
-
Anthropic, “Automate actions with hooks.” code.claude.com/docs/en/hooks-guide ↩↩↩
-
Anthropic, “Hooks guide — Hooks and permission modes.” code.claude.com/docs/en/hooks-guide#hooks-and-permission-modes ↩↩
-
Anthropic, “Hooks reference — JSON output and decision control.” code.claude.com/docs/en/hooks#json-output ↩↩↩↩↩↩↩
-
Anthropic, “Hooks reference — Configuration: hook locations, matcher patterns, hook handler fields, the /hooks menu.” code.claude.com/docs/en/hooks#configuration ↩↩↩↩↩↩
-
Anthropic, “Hooks guide — Limitations and troubleshooting.” code.claude.com/docs/en/hooks-guide#limitations-and-troubleshooting ↩↩↩↩↩
-
Anthropic, “Hooks reference — Security considerations.” code.claude.com/docs/en/hooks#security-considerations ↩
-
Anthropic, “Claude Code settings.” code.claude.com/docs/en/settings ↩