Pular para o conteúdo
Zumkai

Hooks: a única camada que garante

Um hook que nega bloqueia a ferramenta até com bypassPermissions. Hooks apertam restrição e nunca afrouxam, e é isso que os torna a camada de política.

  • claude code
  • hooks
Card mostrando que um hook PreToolUse dispara antes de qualquer checagem de modo de permissão.
Sumário
  1. Os 30 eventos, agrupados pelo que servem
  2. Cinco tipos, e dois que não são determinísticos
  3. A regra que faz o hook valer: aperta, nunca afrouxa
  4. Hooks rodam em paralelo, e isso tem consequência
  5. Onde o hook mora define o alcance dele
  6. Três hooks que valem o esforço
  7. Quando não usar hook
  8. Perguntas frequentes
  9. Fontes

O CLAUDE.md é contexto e o agente pode ignorar. A skill é contexto e o agente pode não invocar. O hook é comando de shell que o Claude Code executa em ponto fixo do ciclo de vida — acontece independentemente do que o modelo decidir.

Essa é a diferença que importa, e ela tem uma prova documentada: um hook PreToolUse que retorna deny bloqueia a ferramenta mesmo em modo bypassPermissions, mesmo com --dangerously-skip-permissions.

Nenhuma outra camada do Claude Code faz isso.

Os 30 eventos, agrupados pelo que servem

A maioria dos textos sobre hooks cobre dois eventos. Existem trinta.

Ciclo da sessão: SessionStart, SessionEnd, Setup, PreCompact, PostCompact, ConfigChange, InstructionsLoaded.

Ciclo do turno: UserPromptSubmit, UserPromptExpansion, Stop, StopFailure, MessageDisplay, Notification.

Ciclo da ferramenta: PreToolUse, PostToolUse, PostToolUseFailure, PostToolBatch, PermissionRequest, PermissionDenied.

Delegação: SubagentStart, SubagentStop, TeammateIdle, TaskCreated, TaskCompleted.

Ambiente: CwdChanged, DirectoryAdded, FileChanged, WorktreeCreate, WorktreeRemove.

MCP: Elicitation, ElicitationResult.

Alguns merecem destaque porque resolvem problemas que as pessoas tentam resolver de outro jeito:

  • InstructionsLoaded dispara quando um CLAUDE.md ou uma regra de .claude/rules/ entra no contexto, inclusive nos carregamentos preguiçosos durante a sessão. É a ferramenta certa para depurar regra com paths que você não sabe se carregou.
  • PostCompact roda depois da compactação — o lugar para reinjetar contexto que não sobrevive ao /compact.
  • CwdChanged dispara quando o agente executa um cd. Serve para gestão reativa de ambiente, no estilo do direnv.
  • FileChanged observa arquivo em disco, com o matcher definindo quais nomes vigiar.

Vários desses eventos existem para resolver armadilhas que degradam em silêncio.

Cinco tipos, e dois que não são determinísticos

Aqui está a parte que contraria a definição corrente de hook.

TipoO que faz
commandRoda comando de shell. É o padrão
httpFaz POST dos dados do evento para uma URL
mcp_toolChama ferramenta de um servidor MCP já conectado
promptAvaliação de uma passada por LLM
agentVerificação multi-turno com acesso a ferramentas — experimental

Os dois últimos existem para decisão que exige julgamento em vez de regra fixa.

Hook de prompt. Em vez de rodar shell, o Claude Code manda o seu prompt e os dados do evento para um modelo (Haiku por padrão), que devolve a decisão em JSON:

json
{
  "hooks": {
    "Stop": [{
      "hooks": [{
        "type": "prompt",
        "prompt": "Verifique se todas as tarefas foram concluídas. Se não, responda {\"ok\": false, \"reason\": \"o que falta\"}."
      }]
    }]
  }
}

Com "ok": false num evento Stop, o reason volta para o Claude como próxima instrução e ele continua trabalhando. Existe uma válvula de escape importante: "impossible": true marca a condição como impossível de satisfazer, e aí o turno encerra em vez de entrar em laço.

Hook de agente. Quando a verificação exige ler arquivo ou rodar comando, o type: "agent" cria um subagente que investiga antes de decidir. Timeout padrão de 60 segundos e até 50 turnos de ferramenta.

json
{
  "type": "agent",
  "prompt": "Verifique se todos os testes unitários passam. Rode a suíte e cheque o resultado. $ARGUMENTS",
  "timeout": 120
}

A documentação marca hooks de agente como experimentais e recomenda hooks de comando para fluxo de produção.

O critério entre os dois: use prompt quando os dados do evento bastam para decidir; use agente quando é preciso verificar contra o estado real do código.

A regra que faz o hook valer: aperta, nunca afrouxa

Esta é a propriedade que transforma hook em camada de política.

PreToolUse dispara antes de qualquer checagem de modo de permissão, em todos os modos, inclusive dontAsk. Um hook que retorna permissionDecision: "deny" bloqueia a ferramenta em bypassPermissions e com --dangerously-skip-permissions.

Em outras palavras: dá para impor política que o usuário não consegue contornar mudando o modo de permissão. Para time com requisito de conformidade, é a única forma documentada de fazer isso.

O inverso não vale. Um hook que retorna allow:

  • não burla regra de deny das configurações
  • não suprime o prompt de ferramenta de conector que a organização marcou como ask
  • não suprime ferramenta MCP marcada com requiresUserInteraction

A assimetria é deliberada, e é o que torna o mecanismo confiável. Hook aperta; nunca solta.

Vale saber também que código de saída 2 bloqueia, e bloqueia forte: mesmo um JSON com permissionDecision: "allow" não sobrepõe um exit 2.

Os eventos de delegação (SubagentStart, SubagentStop, TeammateIdle) fecham o ciclo descrito em subagentes: quando delegar e quando não.

Hooks rodam em paralelo, e isso tem consequência

Quando vários hooks casam com o mesmo evento, todos rodam em paralelo e todos completam antes de o Claude Code juntar os resultados.

A consequência que pega: um hook retornando deny não impede o efeito colateral de um hook irmão.

O exemplo da documentação é claro. Dois hooks em Bash: um registra o comando num log e sai 0; o outro sai 2 para negar quando o comando contém rm -rf. Quando o Claude tenta rodar rm -rf /tmp/build, os dois executam. O comando é bloqueado, e a linha de log é escrita mesmo assim — porque o hook de log já rodou.

Para combinar decisões, a resposta mais restritiva vence, na ordem denydeferaskallow. Já o texto de additionalContext é preservado de todos os hooks e entregue junto.

E há uma armadilha de concorrência: quando mais de um hook PreToolUse devolve updatedInput para reescrever os argumentos de uma ferramenta, o último a terminar vence — e a ordem é não-determinística. A recomendação oficial é não ter dois hooks modificando a entrada da mesma ferramenta.

Onde o hook mora define o alcance dele

São sete lugares possíveis, e a escolha decide quem herda a regra.

OndeAlcanceCompartilhável
~/.claude/settings.jsonTodos os seus projetosNão
.claude/settings.jsonUm projetoSim, versionável
.claude/settings.local.jsonUm projetoNão, fica no gitignore
Configuração gerenciadaA organização inteiraSim, controlada pelo admin
Plugin, em hooks/hooks.jsonOnde o plugin estiver ativoSim, junto com o plugin
Frontmatter de skillO resto da sessão, após a skill ser invocadaSim, dentro do arquivo da skill
Frontmatter de subagenteEnquanto aquele subagente rodaSim, dentro do arquivo do subagente

As duas últimas linhas são as menos conhecidas e as mais interessantes.

Um hook declarado no frontmatter de uma skill é registrado quando a skill é invocada e continua valendo pelo resto da sessão. Isso permite empacotar política junto com o procedimento: a skill que faz deploy pode trazer o hook que impede escrita em produção.

Um hook declarado no frontmatter de um subagente vale só enquanto ele roda. É a forma de dar ao subagente uma restrição que não vale para a sessão principal — o exemplo da documentação valida query somente-leitura antes de deixar o subagente tocar no banco.

Para desligar tudo, "disableAllHooks": true nas configurações. Duas ressalvas: a precedência de configuração se aplica, então o arquivo do projeto pode sobrepor o seu; e hooks de configuração gerenciada continuam rodando a menos que a flag também esteja lá. É coerente com a regra de que hook aperta e não afrouxa.

O /hooks lista tudo o que está configurado, agrupado por evento — e é por onde começar quando algo dispara e você não sabe de onde veio.

Três hooks que valem o esforço

1. Filtrar saída verbosa antes que ela vire contexto. O caso de maior retorno. Em vez de o agente ler um log de 10.000 linhas para achar os erros, o hook faz o filtro e devolve só o que importa:

bash
#!/bin/bash
input=$(cat)
cmd=$(echo "$input" | jq -r '.tool_input.command')

if [[ "$cmd" =~ ^(npm test|pytest|go test) ]]; then
  filtered="$cmd 2>&1 | grep -A 5 -E '(FAIL|ERROR|error:)' | head -100"
  echo "{\"hookSpecificOutput\":{\"hookEventName\":\"PreToolUse\",\"permissionDecision\":\"allow\",\"updatedInput\":{\"command\":\"$filtered\"}}}"
else
  echo "{}"
fi

2. Bloquear caminho protegido. Regra que não depende do agente lembrar:

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

3. Reinjetar contexto depois da compactação. Um PostCompact resolve o problema de instrução que some no meio da sessão — os CLAUDE.md aninhados e as regras com paths não são reinjetados sozinhos.

Para verificar se um hook está ativo, /hooks mostra se ele aparece sob o evento correto. E claude --debug mostra modified tool input keys quando um hook reescreve o comando.

Quando não usar hook

Quando a decisão exige contexto da conversa. Hook de comando se comunica só por stdout, stderr e código de saída. Ele não enxerga o que foi discutido.

Quando você precisa desfazer. PostToolUse roda depois que a ferramenta já executou. Não existe rollback.

Quando "sempre" não é sempre. O Stop dispara toda vez que o Claude termina de responder, não só quando a tarefa acabou. E ele não dispara em interrupção do usuário — erro de API dispara StopFailure, que é outro evento.

Quando o custo de latência não compensa. Os timeouts variam: command, http e mcp_tool têm 10 minutos, mas UserPromptSubmit cai para 30 segundos e MessageDisplay para 10. Hook de prompt tem 30 segundos e o de agent, 60. Os de SessionEnd dividem um orçamento de 1,5 segundo.

E uma nota de depuração que economiza tempo: matchers são case-sensitive. Metade dos "meu hook não dispara" é o nome da ferramenta com a caixa errada.

Vale lembrar que hook também chega de fora: um plugin instalado pode registrar os próprios. O que checar antes está em plugins e marketplaces.

Perguntas frequentes

Hook é melhor que instrução no CLAUDE.md?

São camadas diferentes. O CLAUDE.md orienta comportamento e o agente pode não seguir. O hook executa em ponto fixo do ciclo, independentemente do modelo. Use CLAUDE.md para convenção e hook para o que precisa de garantia.

Dá para impedir que alguém desative meus hooks trocando o modo de permissão?

Sim, e essa é a propriedade central. PreToolUse dispara antes da checagem de modo, então um deny vale mesmo em bypassPermissions e com --dangerously-skip-permissions. O caminho inverso não existe: hook não afrouxa restrição de configuração.

Meu hook não dispara. Por onde começo?

Três checagens, nessa ordem: /hooks confirma que ele aparece sob o evento certo; o matcher precisa casar o nome da ferramenta exatamente, com a mesma caixa; e o evento precisa ser o certo — PreToolUse antes, PostToolUse depois.

Hook de prompt não deixa o mecanismo não-determinístico?

Deixa, e é intencional. Os tipos prompt e agent existem para decisão que exige julgamento. Quando você precisa de garantia determinística, o tipo é command com código de saída — e a documentação recomenda exatamente isso para fluxo de produção.

Fontes

Verificado em 20 de agosto de 2026.

Gatilho de reavaliação: revisar quando (a) hooks de agente saírem do estágio experimental, (b) mudar a lista de 30 eventos ou os timeouts por tipo, ou (c) a precedência entre hook e modo de permissão for alterada — hoje ela é o que sustenta o uso de hook como camada de política.

Leia também