30 eventos de hook disparam em uma sessão do Claude Code. Só 3 podem responder.

A lista completa dos eventos de hook do Claude Code, quando cada um dispara, quais 15 podem bloquear e a regra de stdout que engole silenciosamente a saída da maioria dos hooks. Uma referência de campo construída rodando hooks em produção em milhares de sessões de agentes.

Duas falhas aparecem repetidamente quando as pessoas conectam hooks ao Claude Code, e elas não se parecem em nada.

A primeira: você adiciona um hook e nada acontece. Nenhum erro, nenhum aviso, nenhuma linha de log. O hook simplesmente nunca roda.

A segunda: o hook claramente roda, você vê seus efeitos colaterais no disco, mas a mensagem que ele imprime para o agente nunca chega. O agente se comporta como se o hook não tivesse dito nada.

As duas vêm do mesmo lugar. O sistema de hooks é maior e menos uniforme que o punhado de eventos que a maioria dos artigos cobre, e as regras sobre quem pode falar com o agente não são as que você imaginaria. Esta é a referência que gostaríamos de ter tido. Construímos o AgentsRoom sobre esses hooks, e tudo o que vem abaixo é citado da referência oficial ou medido em produção.

Existem 30 eventos, não seis

A maioria dos guias cobre PreToolUse, PostToolUse, UserPromptSubmit, Stop, Notification e SubagentStop. Esses seis são reais e carregam a maior parte do trabalho útil. Eles também são um quinto do que existe.

A lista completa, agrupada pelo que cada um observa:

GrupoEventos
SessãoSessionStart, SessionEnd, Setup
PromptUserPromptSubmit, UserPromptExpansion
FerramentasPreToolUse, PostToolUse, PostToolUseFailure, PostToolBatch
PermissõesPermissionRequest, PermissionDenied
TurnoStop, StopFailure
Subagentes e tarefasSubagentStart, SubagentStop, TaskCreated, TaskCompleted, TeammateIdle
ContextoPreCompact, PostCompact, InstructionsLoaded
AmbienteFileChanged, CwdChanged, ConfigChange
WorktreesWorktreeCreate, WorktreeRemove
InterfaceNotification, MessageDisplay
Elicitação MCPElicitation, ElicitationResult

Diagrama cronológico dos 30 eventos de hook do Claude Code na ordem em que disparam durante uma sessão de agente, de SessionStart até SessionEnd passando por UserPromptSubmit, PreToolUse, PostToolUse e Stop, mostrando quais eventos podem bloquear o agente.

A ordem em que os eventos disparam em uma mesma sessão. O bloco de ferramenta se repete a cada chamada de ferramenta, e o bloco de prompt inteiro se repete a cada turno.

Alguns deles mudam a forma como você pensa o sistema. PostToolUseFailure existe, então o ramo "a ferramenta funcionou?" é um evento, não algo que você deduz de um payload. PostToolBatch dispara uma vez depois que um lote de chamadas de ferramenta paralelas se resolve, que é o lugar certo para rodar um linter uma vez em vez de uma vez por edição. InstructionsLoaded dispara quando o CLAUDE.md é lido, o que te dá um ponto de hook para verificar se o agente realmente carregou as regras que você acha que ele carregou.

A regra de stdout que engole a saída da maioria dos hooks

Esta é a coisa mais útil desta página.

Com código de saída 0, o Claude Code analisa o stdout em busca de campos JSON de saída. Mas se esse stdout chega ou não a ser mostrado ao agente depende do evento, e as exceções cabem em uma lista curta. Da referência:

Para a maioria dos eventos, o stdout é escrito no log de debug, mas não é exibido na transcrição. As exceções são UserPromptSubmit, UserPromptExpansion e SessionStart, onde o stdout é adicionado como contexto que o Claude pode ver e usar.

Três eventos de trinta. Se você fizer echo "warning: this migration is destructive" a partir de um hook PostToolUse esperando que o agente leia, ele nunca vai ler. Seu texto foi para o log de debug.

Existem exatamente duas formas de colocar texto diante do agente a partir de qualquer outro evento:

  1. Saia com 2 e escreva no stderr. Com saída 2, o Claude Code ignora o stdout e qualquer JSON nele, e devolve o stderr ao agente como mensagem de erro.
  2. Saia com 0 e imprima um objeto JSON carregando hookSpecificOutput.additionalContext.

Repare na assimetria do primeiro. Saída 0 significa que o stdout conta e o stderr não. Saída 2 significa que o stderr conta e o stdout é descartado por completo. Inverter isso é o motivo pelo qual um hook pode parecer completamente correto e ainda assim ficar mudo.

Qualquer outro código de saída é um erro não bloqueante. A transcrição mostra um aviso <hook name> hook error com a primeira linha do stderr, a execução continua, e o stderr completo vai parar no log de debug.

Diagrama dos códigos de saída dos hooks do Claude Code: a saída 0 envia o JSON do stdout ao agente apenas em três eventos, a saída 2 bloqueia a ação e envia o stderr ao agente, qualquer outro código de saída é um erro não bloqueante escrito no log de debug.

Qual canal chega ao agente, por código de saída. O caminho tracejado é aquele que as pessoas supõem existir e que não existe.

Exatamente metade deles pode bloquear

Quinze eventos interrompem a ação com saída 2. Quinze ignoram e seguem em frente.

Podem bloquear: PreToolUse, PermissionRequest, UserPromptSubmit, UserPromptExpansion, Stop, SubagentStop, TeammateIdle, TaskCreated, TaskCompleted, ConfigChange, PostToolBatch, PreCompact, Elicitation, ElicitationResult, WorktreeCreate.

Não podem bloquear: PostToolUse, PostToolUseFailure, PermissionDenied, StopFailure, Notification, SubagentStart, SessionStart, Setup, SessionEnd, CwdChanged, FileChanged, PostCompact, WorktreeRemove, InstructionsLoaded, MessageDisplay.

A consequência prática: uma trava de segurança pertence ao PreToolUse, nunca ao PostToolUse. PostToolUse dispara depois que a ferramenta teve sucesso. Sair com 2 ali não desfaz a escrita, apenas imprime um erro enquanto o estrago já está no disco. Se você quer impedir um rm -rf, existe exatamente um lugar para fazer isso.

O fato de PostToolBatch bloquear enquanto PostToolUse não bloqueia merece um segundo olhar. Significa que uma verificação no nível do lote ainda pode parar o turno depois que edições paralelas caíram no disco, o que é o mais próximo de um veto pós-escrita que o sistema oferece.

Os matchers são exatos, até que de repente não são

O campo matcher muda de estratégia de avaliação conforme os próprios caracteres que ele contém, e nada te diz qual caminho ele tomou.

MatcherAvaliado como
"*", "", ou omitidocorresponde a tudo
Apenas letras, dígitos, _, -, espaços, ,, |string exata, ou lista de strings exatas separadas por | ou ,
Qualquer outra coisaexpressão regular JavaScript não ancorada

A armadilha é o "não ancorada". A referência é explícita: a regex é testada com RegExp.prototype.test, que dá certo com uma correspondência em qualquer posição do valor. Então Edit.* corresponde a Edit e a NotebookEdit. Se você queria uma única ferramenta, escreva ^Edit$.

Dois comportamentos dependentes da versão que vale conhecer antes de depurar a coisa errada:

  • Separadores por vírgula e tolerância a espaços exigem o Claude Code v2.1.191 ou mais recente.
  • Os hífens entraram no conjunto de caracteres de correspondência exata na v2.1.195. Antes disso, um matcher como code-reviewer era tratado como uma regex não ancorada, então ele também disparava para senior-code-reviewer.

No AgentsRoom, restringimos nosso próprio hook de atribuição de arquivos com Write|Edit|MultiEdit|NotebookEdit, o que permanece no caminho da string exata e corresponde a essas quatro ferramentas e a nada mais. Os hooks de ciclo de vida que instalamos não têm matcher nenhum, porque sempre nos dizem respeito.

Seis lugares podem definir hooks, e eles se fundem

O instinto é procurar uma ordem de precedência. Não existe uma, e é justamente essa a parte interessante.

LocalEscopo
~/.claude/settings.jsontodos os seus projetos, local à sua máquina
.claude/settings.jsonum projeto, versionável
.claude/settings.local.jsonum projeto, colocado no gitignore pelo Claude Code
Managed policy settingstoda a organização, controlado pelo admin
hooks/hooks.json de pluginenquanto o plugin estiver ativado
Frontmatter de skill ou de agenteenquanto o componente estiver ativo

Da referência:

As entradas de hook se fundem entre os níveis de settings em vez de se substituírem: os settings de usuário, de projeto e locais adicionam seus próprios hooks sem remover os gerenciados, e a configuração disableAllHooks não pode desativar hooks gerenciados a partir de fora dos settings gerenciados.

Ou seja, um hook de projeto nunca sobrescreve um global, ele se empilha em cima. Seis fontes, todas aditivas. Um formatador PostToolUse definido nos seus settings de usuário e de novo no projeto roda duas vezes por edição, e o único sintoma é a sensação de lentidão.

Diagrama mostrando os seis locais de settings do Claude Code que podem definir hooks, todos se fundindo de forma aditiva em um único conjunto de hooks em vez de se sobrescreverem.

Seis fontes, um único conjunto fundido. Nada aqui sobrescreve nada.

Isso também explica por que .claude/settings.local.json é o lugar certo para uma ferramenta instalar um hook no projeto de alguém. Ele tem escopo de projeto, o Claude Code o coloca no gitignore, e é carregado sem nenhuma flag de CLI. É ali que o AgentsRoom escreve suas entradas, para que o .claude/settings.json versionado do usuário nunca seja tocado e os colegas dele nunca herdem um caminho específico de uma máquina.

O que rodar hooks em produção nos ensinou

O AgentsRoom instala hooks em todo projeto que abre, para acompanhar o status dos agentes de forma determinística e atribuir os arquivos editados ao agente certo. Algumas coisas só aparecem nessa escala.

Nomes de evento desconhecidos são ignorados em silêncio. Isso não está na documentação, e nós dependemos disso. Quando adicionamos um novo evento de ciclo de vida ao nosso instalador, usuários com um CLI mais antigo recebem um settings.local.json contendo um nome de evento do qual o binário deles nunca ouviu falar. Nada quebra, nada avisa, a entrada é ignorada. É isso que torna o instalador seguro de publicar antes de uma versão do CLI. É também, inevitavelmente, por que um erro de digitação produz silêncio total em vez de um erro.

agent_id é como você sabe que está dentro de um subagente. O campo só está presente quando o hook dispara dentro de uma chamada de subagente. Isso importa mais do que parece: Stop dispara quando um subagente termina o turno dele, não só o agente principal. Uma regra ingênua do tipo "marcar a sessão como concluída no Stop" marca a sessão inteira como terminada na primeira vez que qualquer subagente retorna. Nós ignoramos os eventos de fim de turno que carregam agent_id exatamente por isso.

Não leia transcript_path para o turno atual. A referência avisa que a transcrição é escrita de forma assíncrona e pode ficar atrás da conversa em memória, então as mensagens mais recentes podem ainda não estar lá quando seu hook dispara. Stop e SubagentStop recebem last_assistant_message justamente para você nunca ter que competir com o arquivo.

Os hooks são o único sinal de status confiável. Antes dos hooks, nós raspávamos o PTY para descobrir se um agente estava pensando, esperando ou pronto. Isso quebra no momento em que o CLI renderiza pelo buffer de tela alternativo do terminal, que é o que /tui fullscreen faz. Os hooks disparam de forma idêntica sob qualquer renderizador. Se você está construindo qualquer coisa que observa um agente de fora, esta é a camada sobre a qual construir, e a raspagem fica, na melhor das hipóteses, como plano B.

async: true não custa nada. Um comando de hook pode declarar async: true, e o agente não espera por ele. Nosso hook faz um POST para um endpoint local com limite de 2 segundos e retorna; a latência do turno do agente não é afetada nem quando o aplicativo receptor está fechado. Se seu hook só observa e nunca decide, torne-o async e pare de pagar por ele.

Nunca deixe um hook escrever lixo no terminal. Nosso script engole toda exceção, inclusive no nível mais alto. Um traceback não tratado vindo de um hook não falha só discretamente: ele imprime um stack trace Python na sessão de terminal do usuário, no meio do trabalho dele.

Tempos limite

Os valores padrão são generosos, com três exceções que não são:

Tipo de hookTempo limite padrão
command, http, mcp_tool600 s
prompt30 s
agent60 s
UserPromptSubmit (command, http, mcp_tool)30 s
MessageDisplay (command, http, mcp_tool)10 s
SessionEnd1,5 s compartilhado entre todos os hooks, elevado para acompanhar um timeout por hook mais longo, até 60 s

O orçamento do SessionEnd é o que surpreende as pessoas. É um orçamento compartilhado, não uma cota por hook, então três hooks de limpeza dividem 1,5 segundo entre si, a menos que você o eleve explicitamente.

A versão curta

  • 30 eventos existem. Seis são famosos.
  • o stdout chega ao agente apenas em UserPromptSubmit, UserPromptExpansion e SessionStart. Em todo o resto, use saída 2 com stderr, ou additionalContext em JSON.
  • 15 eventos bloqueiam com saída 2, 15 ignoram. As travas de segurança vão no PreToolUse.
  • Os matchers são strings exatas até que um caractere especial os transforme em uma regex não ancorada.
  • Seis fontes de settings se fundem de forma aditiva. Nada sobrescreve nada.
  • Um nome de evento escrito errado falha em silêncio absoluto.

Se você prefere ver esses eventos dispararem em vez de raciocinar sobre eles, foi isso que construímos: o AgentsRoom mostra cada disparo de hook por agente, por projeto, por execução, em dezenas de agentes em paralelo e sessões de subagentes. Os hooks que você configura nos seus próprios settings continuam funcionando exatamente como escritos, porque ele roda o CLI de verdade.

Continue lendo

Baixar AgentsRoom

Rode seus agentes de IA (Claude, Codex, Antigravity CLI, OpenCode, Aider, Grok Build, Mistral Vibe, Kimi Code) em todos os seus projetos, de uma única janela.

GratisBaixar AgentsRoom

App complementar: acompanhe seus agentes em qualquer lugar

Use Claude, Codex, Antigravity CLI ou outro provedor de IA.

Instalar a extensão
Chrome Web Store

Envie bugs e pedidos direto para o seu backlog público.

Uma visão do AgentsRoom em ação.

Multi-projetos
Multi-provedor
Multi-agentes
Status ao vivo
Diff e commit
App mobile
Preview ao vivo
Equipes de agentes
Testes no navegador
Dev guiada por backlog
Biblioteca de prompts
Biblioteca de skills
Ver todas as funcionalidades