Pular para o conteúdo principal

Hooks: Automação Determinística

Avançado

Hooks são comandos de shell que o Claude Code executa automaticamente em pontos definidos do seu ciclo de vida. Enquanto as permissões decidem se uma ação é permitida, os hooks permitem que você execute uma lógica determinística em torno dela — formatação, validação, registro de logs, barreiras, ocultação de dados. É assim que você torna o comportamento garantido em vez de "por favor, lembre-se de".

What you'll learn
  • Quando recorrer a um hook em vez de uma instrução, uma permissão ou uma skill
  • O ciclo de vida completo: eventos de sessão, prompt, ferramenta, subagente, tarefa, compactação e fim de turno
  • Como um hook é configurado: evento, matcher e o payload JSON no stdin
  • As duas formas pelas quais um hook bloqueia uma ação — código de saída 2 vs JSON no stdout
  • Os campos universais de saída JSON compartilhados entre eventos: continue, additionalContext, systemMessage, reloadSkills, sessionTitle, suppressOutput, updatedInput
  • As boas práticas e os erros comuns que separam hooks rápidos e seguros dos lentos e silenciosos

Quando recorrer a um hook​

Recorra a um hook quando quiser que um comportamento seja garantido, não apenas solicitado. Cada tarefa comum mapeia para um evento do ciclo de vida:

  • Formatar / fazer lint automaticamente após cada edição de arquivo (PostToolUse em Edit|Write).
  • Bloquear uma ação que viola uma regra antes que ela seja executada (PreToolUse).
  • Ocultar dados da mensagem do assistente antes que ela chegue ao terminal (MessageDisplay).
  • Recarregar skills ou definir um título de sessão após um SessionStart.
  • Notificar ou registrar em log quando uma sessão termina, uma tarefa é concluída ou um turno falha (Stop, StopFailure, SessionEnd).
  • Injetar contexto no início da sessão ou quando as instruções são carregadas.

Como funcionam​

Você registra hooks em settings.json, associando um evento (e, muitas vezes, um matcher — geralmente o nome de uma ferramenta, às vezes um tipo de subagente, um gatilho de compactação ou um tipo de notificação). Quando o evento dispara, o Claude executa seu comando, passando um payload JSON no stdin (o nome do evento, a sessão e campos específicos do evento, como o nome da ferramenta e suas entradas). O código de saída e o stdout do seu comando decidem o que acontece em seguida.

Guided walkthrough1 of 4
  1. Registre o hook em settings.json sob o evento do ciclo de vida que lhe interessa — por exemplo, PostToolUse para automação após a ferramenta, ou MessageDisplay para ocultar dados.
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{ "type": "command", "command": "jq -r '.tool_input.file_path' | xargs npx prettier --write" }
]
}
]
}
}

O hook acima lê o caminho do arquivo editado a partir do JSON de stdin (.tool_input.file_path) e o formata. Não presuma que uma variável de ambiente contém o caminho — leia-o do stdin. Placeholders de caminho úteis como ${CLAUDE_PROJECT_DIR} estão disponíveis para localizar scripts.

O ciclo de vida em resumo​

Os hooks do Claude Code abrangem a sessão inteira, não apenas as chamadas de ferramenta. Agrupe-os por fase para saber onde pendurar sua lógica.

FaseEventosUso típico
SessãoSessionStart, SessionEnd, Setup, InstructionsLoaded, ConfigChange, CwdChanged, DirectoryAdded, FileChangedInjetar contexto no início, fazer um snapshot no fim, reagir a um recarregamento de CLAUDE.md ou a uma mudança de .env em disco
PromptUserPromptSubmit, UserPromptExpansionColocar uma barreira ou reescrever o prompt antes que o Claude o veja
FerramentaPreToolUse, PermissionRequest, PermissionDenied, PostToolUse, PostToolUseFailure, PostToolBatchPermitir/negar uma ação, escalar para uma pessoa, formatar após edições, tentar de novo após uma negação do modo automático
SubagenteSubagentStart, SubagentStopRegistrar início/parada, impedir que um subagente retorne
TarefaTaskCreated, TaskCompletedReverter uma tarefa acidental, colocar uma barreira na conclusão
CompactaçãoPreCompact, PostCompactFazer um snapshot da transcrição antes da compactação, verificar depois
ModeloPreModelSwitch, PostModelSwitchBloquear uma troca de modelo antes de ser aplicada; reagir depois que o modelo muda, inclusive quando uma sessão retomada o restaura
TurnoStop, StopFailure, Notification, MessageDisplayNotificar na conclusão, alertar sobre falha da API, ocultar dados do texto exibido
Equipe + MCPTeammateIdle, Elicitation, ElicitationResult, WorktreeCreate, WorktreeRemoveCoordenação entre sessões, barreira em elicitations do MCP, encapsular operações de worktree

Os campos exatos de stdin de cada evento, a gramática de matcher e as regras de bloqueio estão na referência oficial de hooks. O catálogo acima é o mapa; a referência é o território.

Como um hook bloqueia​

Duas formas, dependendo do evento:

  • Código de saída 2 — o hook faz a ação falhar e o que quer que ele tenha escrito no stderr se torna a mensagem que o Claude vê. Simples e funciona para hooks de comando em eventos bloqueantes (PreToolUse, UserPromptSubmit, UserPromptExpansion, Stop, SubagentStop, TeammateIdle, TaskCreated, TaskCompleted, ConfigChange, PostToolBatch, PreCompact, PreModelSwitch, Elicitation, ElicitationResult, WorktreeCreate, WorktreeRemove). Em PermissionRequest a saída 2 é ignorada; negue por meio do objeto decision dele.
  • JSON no stdout (saída 0) — retorne uma decisão estruturada. Para PreToolUse, isso é uma permissionDecision de "allow", "deny", "ask" (colocar a chamada diante do usuário) ou "defer" (apenas para execuções -p conduzidas por um app que as retoma), com uma permissionDecisionReason. Eventos não bloqueantes (PostToolUse, Notification, MessageDisplay, StopFailure, a maioria dos eventos de sessão) ignoram tentativas de bloqueio — mas a saída JSON deles ainda pode orientar o que o Claude vê em seguida.

O script abaixo é um hook PreToolUse na ferramenta Bash. Leia-o de cima para baixo: ele extrai o comando do stdin e, se ele parecer destrutivo, escreve uma razão no stderr e sai com código 2 para bloquear.

#!/usr/bin/env bash
# PreToolUse hook on the Bash tool: refuse to delete things.
command=$(jq -r '.tool_input.command' < /dev/stdin)
if [[ "$command" == rm\ * || "$command" == *"rm -rf"* ]]; then
echo "Blocked: destructive 'rm' is not allowed by policy." >&2
exit 2
fi
exit 0

Campos universais de saída JSON​

A maioria dos eventos aceita um objeto JSON estruturado no stdout que orienta o que acontece em seguida — separado dos campos de decisão específicos do evento, como permissionDecision. Os mais úteis:

CampoO que fazOnde funciona
continueSe false, o Claude para de processar por completo — tem precedência sobre toda decisão específica de eventoA maioria dos eventos
systemMessageAdiciona uma mensagem ao contexto / transcrição do Claude para que ele veja por que você agiuA maioria dos eventos bloqueantes
additionalContextInjeta contexto extra que o Claude pode ler e usar no próximo turnoSessionStart, UserPromptSubmit, outros
reloadSkillsReescaneia os diretórios de skills para que a sessão pegue uma skill que você acabou de escreverSessionStart (e outros eventos de sessão)
sessionTitleDefine o título da sessão na UI — sob hookSpecificOutput em SessionStartSessionStart
suppressOutputOculta a saída da ferramenta do Claude (útil para comandos ruidosos)Eventos de ferramenta
updatedInputReescreve a entrada da ferramenta antes de ela rodar (ex.: adicionar um flag --dry-run)PreToolUse
terminalSequenceEmite sequências de escape do terminal — sino, título da janela, selo de notificaçãoNotification, StopFailure, outros

Os campos de decisão específicos de evento — permissionDecision ("allow", "deny", "ask" ou "defer", mais permissionDecisionReason) em PreToolUse, decision.behavior ("allow" ou "deny") em PermissionRequest, e retry: true em PermissionDenied para deixar o Claude tentar de novo a chamada negada — são aplicados por cima destes universais.

O modelo mental​

Um hook PreToolUse roda antes da ação e pode bloqueá-la; um hook PostToolUse roda depois que ela tem sucesso e reage ao resultado.

Eventos de hook em resumo
Pressione Enter ou Espaço para virar o cartão. Use as setas esquerda e direita para navegar entre os cartões.Termo exibido.
1 / 6

Boas práticas​

  • Mantenha os hooks rápidos e idempotentes — eles rodam muito. Os hooks SessionEnd compartilham um único orçamento de 1,5 segundo entre todos, então mantenha o trabalho de fim de sessão mínimo.
  • Falhe de forma ruidosa em problemas reais, mas não bloqueie por questões cosméticas.
  • Trate a saída do hook como feedback para o Claude — uma mensagem clara no stderr (saída 2) ou em systemMessage (JSON) o ajuda a se autocorrigir.
  • Prefira a saída JSON aos códigos de saída quando quiser dizer mais do que "bloqueado" — permissionDecision: "ask" em PreToolUse encaminha a uma pessoa sem bater a porta, e additionalContext permite conduzir em vez de apenas recusar.
  • Hooks rodam com os privilégios do seu shell — revise qualquer hook que você não escreveu (Revisando Código de Terceiros).

Erros comuns​

  • Ler o caminho do arquivo a partir de uma variável de ambiente. O caminho vive no JSON de stdin (.tool_input.file_path), não em $CLAUDE_FILE_PATH. Encaminhe o stdin através do jq.
  • Bloqueios silenciosos. Se um hook PreToolUse sai com código 2 sem nada no stderr, o Claude é bloqueado, mas não sabe por quê e não consegue se adaptar. Sempre escreva uma razão clara (ou use permissionDecisionReason).
  • Tentar bloquear um evento não bloqueante. PostToolUse dispara depois que a ferramenta rodou — uma saída 2 apenas sussurra uma mensagem ao Claude, não desfaz a ação. Se precisar de uma barreira, use PreToolUse.
  • Hooks lentos. Um hook PostToolUse roda após cada edição correspondente. Um linter de 3 segundos faz toda a sessão parecer lenta — mantenha os hooks rápidos e, idealmente, atue apenas sobre o que mudou.
  • Matchers excessivamente amplos. matcher: ".*" dispara em cada ferramenta. Restrinja com um nome exato, uma lista Edit|Write ou o campo if por handler (ex.: "if": "Bash(git push *)").
  • Esquecer que matchers existem também para eventos que não são de ferramenta. SessionStart corresponde a startup|resume|clear|compact|fork; Notification corresponde a tipos como permission_prompt|idle_prompt|agent_completed; PreCompact corresponde a manual|auto. Um único hook ligado ao subconjunto errado dispara ou demais ou nunca.
  • Confiar em hooks que você não escreveu. Um hook executa shell arbitrário com os seus privilégios. Revise primeiro qualquer hook vindo de um plugin ou template — veja Revisando Código de Terceiros.
Watch out
  • Um hook executa shell arbitrário com os seus privilégios — nunca configure um hook de um plugin ou template sem lê-lo primeiro.

Receitas que valem a pena copiar​

Modelos prontos para copiar e colar estão em Receitas de Hooks e settings.json. Algumas receitas de alto impacto para ver o formato dos eventos mais novos:

Formatar automaticamente arquivos editados (PostToolUse em Edit|Write)

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

Definir um título de sessão por projeto ao iniciar (SessionStart)

{
"hooks": {
  "SessionStart": [
    {
      "matcher": "startup|resume",
      "hooks": [
        {
          "type": "command",
          "command": "jq -nc --arg t \"$(basename \"$PWD\") — $(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo detached)\" '{hookSpecificOutput:{sessionTitle:$t}}'"
        }
      ]
    }
  ]
}
}

Recarregar skills após um SessionStart (pega uma skill que você acabou de escrever)

{
"hooks": {
  "SessionStart": [
    {
      "matcher": "startup|resume",
      "hooks": [
        { "type": "command", "command": "printf '{\"reloadSkills\": true}\n'" }
      ]
    }
  ]
}
}

Ocultar chaves de API do texto exibido do assistente (MessageDisplay)

{
"hooks": {
  "MessageDisplay": [
    {
      "hooks": [
        {
          "type": "command",
          "command": "jq -r '.message.content' | sed -E 's/(sk-[A-Za-z0-9_-]{20,})/sk-***REDACTED***/g'"
        }
      ]
    }
  ]
}
}

Notificar quando um turno falha (StopFailure — limites de taxa, sobrecarga, autenticação)

{
"hooks": {
  "StopFailure": [
    {
      "matcher": "rate_limit|overloaded|authentication_failed|billing_error",
      "hooks": [
        { "type": "command", "command": "osascript -e 'display notification \"Claude Code turn failed — check the terminal\" with title \"Claude Code\"'" }
      ]
    }
  ]
}
}

Teste seus conhecimentos

0/4
  1. Onde um hook encontra o caminho do arquivo que acabou de ser editado?
  2. Um hook PreToolUse sai com código 2. O que acontece?
  3. Você quer que uma sessão pegue uma skill que acabou de adicionar em .claude/skills/ sem reiniciar. Qual campo de saída, em qual evento?
  4. Você quer ocultar segredos do texto do Claude antes que ele apareça na tela do usuário. Qual evento?
Key takeaways
  • Hooks tornam o comportamento garantido, não solicitado — lógica determinística em torno de ações que as permissões apenas permitem ou negam.
  • O ciclo de vida abrange mais de 30 eventos nas fases de sessão, prompt, ferramenta, subagente, tarefa, compactação e turno — escolha a fase certa antes de escrever o script.
  • Registre em settings.json contra um evento mais um matcher; o Claude canaliza um payload JSON no stdin e lê seu código de saída e sua saída.
  • Leia o caminho do arquivo do stdin (.tool_input.file_path) — não de uma variável de ambiente.
  • Bloqueie com o código de saída 2 (o stderr se torna a mensagem) ou com JSON estruturado no stdout (permissionDecision em eventos de ferramenta, {continue: false} para parar tudo).
  • Os campos universais de saída — additionalContext, systemMessage, reloadSkills, sessionTitle, suppressOutput, updatedInput — permitem conduzir o Claude em vez de apenas permitir ou recusar.
  • Mantenha os hooks rápidos, idempotentes e com matchers estreitos — e revise qualquer hook que você não escreveu, já que ele roda com os privilégios do seu shell.

Próximos passos​