Hooks do Claude Code: eventos, código 2 e segurança
Entenda onde configurar hooks do Claude Code, quais eventos são acionados e como o código de saída 2 bloqueia chamadas de ferramentas antes da execução.
O que é um hook do Claude Code
Os hooks do Claude Code são comandos shell que o Claude Code executa automaticamente em pontos fixos do seu próprio ciclo de vida. Essa é a diferença entre um hook e um ficheiro de regras. Uma instrução em CLAUDE.md é uma orientação, e o modelo pondera-a juntamente com tudo o que estiver no seu contexto. Um hook é código, e é executado independentemente de o modelo concordar ou não. Se o seu agente continua a ignorar o formatador que já lhe indicou duas vezes, não precisa de uma instrução mais firme. Precisa de um hook.
O mecanismo é simples. Regista um comando num ficheiro de definições, associado ao nome de um evento. Quando o evento ocorre, o Claude Code executa o comando e escreve os dados do evento na entrada padrão (stdin) como JSON (JavaScript object notation). O comando lê esses dados, executa o trabalho e devolve um código de saída. O código de saída 2 de um hook PreToolUse cancela a chamada da ferramenta antes da sua execução, e tudo o que o script escrever na saída de erro padrão (stderr) é devolvido ao modelo como motivo.
Os nomes dos eventos e dos campos apresentados aqui vêm da referência de hooks do Claude Code, verificada em agosto de 2026 com a versão 2.1.232. Esta interface muda rapidamente. Consulte a referência relativa à sua versão antes de copiar JSON de qualquer publicação, incluindo esta. Apresente a sua versão com claude --version.
Onde fica a configuração dos hooks
Um hook é um bloco JSON num ficheiro de definições. Seis locais podem conter um hook, e o âmbito do ficheiro define o âmbito do hook.
~/.claude/settings.json: todos os projetos na sua máquina, e em nenhuma outra..claude/settings.json: um projeto, submetido ao repositório, para que todas as pessoas que o clonem recebam o hook..claude/settings.local.json: um projeto, apenas na sua máquina.- Definições de política gerida: aplicadas a toda a organização e configuradas por um administrador.
hooks/hooks.jsondentro de um plugin, ativo enquanto esse plugin estiver ativado.- Frontmatter de uma skill ou de um subagente, ativo enquanto esse componente estiver ativo.
As entradas de hooks destes ficheiros são combinadas, em vez de se substituírem. Um ficheiro de definições do projeto adiciona os seus hooks aos hooks das definições do utilizador, em vez de os substituir. Assim, um evento pode conter vários hooks provenientes de vários ficheiros. Definir "disableAllHooks": true desativa-os, com uma exceção: os hooks das definições de política gerida continuam a ser executados, a menos que essa definição também seja aplicada nas definições geridas.
Execute /hooks numa sessão para listar todos os hooks registados, agrupados por evento, com o ficheiro de origem e o matcher de cada um. O menu é apenas de leitura. Para alterar um hook, edite o ficheiro de definições. Normalmente, o file watcher deteta a alteração sem reiniciar.
Quais eventos de hooks existem no Claude Code
A versão 2.1.232 lista trinta e um eventos, de SessionStart a SessionEnd, abrangendo compactação, subagentes, worktrees e ficheiros de configuração. O trabalho de administração de servidores usa alguns deles.
PreToolUse: antes da execução de uma chamada de ferramenta. Este é o evento que pode bloquear a execução.PostToolUse: depois de uma chamada de ferramenta ser concluída com sucesso.PostToolUseFailureé acionado quando ela falha. Portanto, um hook que precise de observar todos os resultados deve usar ambos.PermissionRequest: quando uma chamada de ferramenta precisa de uma decisão de permissão, no momento em que o pedido de aprovação seria apresentado.UserPromptSubmit: quando envia um prompt, antes de o Claude o processar. Qualquer conteúdo que este hook escreva em stdout é adicionado ao contexto do modelo.SessionStarteSessionEnd: no início e no fim de cada sessão.SessionStarttambém é acionado depois da compactação, com o valorcompactno matcher.Stop: quando o Claude termina de responder. Isto ocorre uma vez por turno, não uma vez por tarefa concluída.
Cada grupo inclui um matcher que determina em que ocorrências o hook é executado. Nos eventos de ferramentas, ele filtra pelo nome da ferramenta. Assim, "Edit|Write" é acionado em edições de ficheiros e em nenhuma outra operação. Os matchers diferenciam maiúsculas de minúsculas. Um matcher vazio é acionado em todas as ocorrências. As ferramentas de um servidor MCP (model context protocol) usam nomes no formato mcp__<server>__<tool>. Portanto, um matcher com o valor "mcp__github__.*" captura as ferramentas de um servidor e ignora as restantes.
Os hooks Stop têm uma particularidade importante antes de serem implementados. Um hook Stop que bloqueia envia o modelo novamente para o processamento, e o Claude Code substitui o hook depois de oito bloqueios consecutivos. Leia o campo stop_hook_active da entrada do hook e termine com o código 0 quando o valor for verdadeiro. Caso contrário, o hook ficará num ciclo até atingir esse limite.
O que um hook recebe em stdin
Quando Claude está prestes a executar npm test, um hook PreToolUse em Bash lê estes dados em stdin:
{
"session_id": "abc123",
"cwd": "/home/deploy/myproject",
"hook_event_name": "PreToolUse",
"tool_name": "Bash",
"tool_input": {
"command": "npm test"
}
}Todos os eventos contêm session_id, cwd, permission_mode, transcript_path e hook_event_name. Os eventos de ferramentas também incluem tool_name, tool_input e tool_use_id. Os outros eventos contêm os seus próprios campos: UserPromptSubmit recebe o texto prompt, e SessionStart recebe um source de startup, resume, clear, compact ou fork.
jq é a forma habitual de ler estes dados num script de shell, mas uma imagem mínima de servidor não o inclui. Instale-o primeiro com sudo apt install -y jq no Ubuntu e no Debian.
O que o estado de saída faz à chamada da ferramenta em curso
Existem três resultados.
- Exit 0 significa que o seu hook não apresenta objeções. Em
PreToolUse, isso não é o mesmo que aprovação, e o fluxo normal de permissões continua. EmUserPromptSubmiteSessionStart, o stdout é adicionado ao contexto do modelo. - Exit 2 bloqueia a ação nos eventos que podem ser bloqueados, incluindo
PreToolUse, e o stderr torna-se o motivo apresentado ao modelo. Nos eventos que não podem ser bloqueados, comoPostToolUse, o bloqueio é ignorado, mas o stderr continua a ser enviado ao modelo como feedback. - Qualquer outro código de saída é um erro que não bloqueia a ação. A ação continua. A transcrição mostra um aviso de erro do hook com a primeira linha do stderr depois do texto
Failed with non-blocking status code:.
Para qualquer comportamento além de bloquear ou permanecer silencioso, use exit 0 e imprima um objeto JSON no stdout. Um hook PreToolUse toma a decisão com permissionDecision:
{
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "deny",
"permissionDecisionReason": "Database drops go through a migration, not through the agent."
}
}"allow" ignora o prompt interativo, "deny" cancela a chamada e envia o motivo ao modelo, e "ask" mostra o prompt normalmente. Escolha um estilo por hook. Misturar exit 2 com uma decisão JSON no stdout produz um resultado que terá de consultar.
Quando vários hooks correspondem ao mesmo evento, são executados em paralelo e todos são executados até ao fim. Um deny de um hook não interrompe os restantes, por isso um hook de registo continua a escrever a sua linha enquanto um hook de proteção recusa a mesma chamada. O Claude Code combina depois as respostas e mantém a mais restritiva, pela ordem deny, defer, ask, allow.
Exemplo 1: bloquear um comando destrutivo antes de ser executado
Guarde este conteúdo como .claude/hooks/block-destructive.sh no seu projeto:
#!/bin/bash
# Deny a Bash tool call whose command matches a banned pattern.
INPUT=$(cat)
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty')
for pattern in 'rm -rf /' 'mkfs' 'dd if=' 'DROP TABLE'; do
if printf '%s' "$COMMAND" | grep -qiF -- "$pattern"; then
echo "Blocked by policy: the command matches '$pattern'. A human runs this one." >&2
exit 2
fi
done
exit 0Torne-o executável e registe-o em PreToolUse em .claude/settings.json:
chmod +x .claude/hooks/block-destructive.sh{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/block-destructive.sh",
"timeout": 10,
"statusMessage": "Checking the command against policy"
}
]
}
]
}
}Teste o script manualmente antes de confiar nele, porque um hook que falha ao processar a própria entrada permite a operação:
echo '{"tool_name":"Bash","tool_input":{"command":"rm -rf /var/lib/postgresql"}}' \
| .claude/hooks/block-destructive.sh
echo $?Deve ver a linha Blocked by policy: em stderr e um código de saída 2. Envie-lhe um comando inofensivo, como ls -la, e não deve ver qualquer saída, com um código de saída 0. Numa sessão, a chamada bloqueada aparece no transcript com a sua mensagem como motivo, e o modelo lê essa mensagem e adapta-se.
Uma propriedade torna isto útil: os hooks PreToolUse são executados antes da verificação do modo de permissões, em todos os modos de permissões, pelo que uma rejeição mantém-se mesmo com bypassPermissions. É isso que torna um hook útil em conjunto com o modo automático do Claude Code e as respetivas definições de permissões, em que os pedidos de confirmação são reduzidos, mas o hook continua a ser executado.
Seja claro sobre o que isto é. A correspondência de padrões numa cadeia de comando é uma barreira contra a execução descuidada por parte de um agente. Não é uma fronteira contra um agente que tente contorná-la, porque o mesmo comando pode ser escrito de uma forma que o seu grep nunca deteta. As regras rígidas devem estar no sistema de permissões e na conta com que o processo é executado.
Exemplo 2: formatar e validar depois de cada edição
PostToolUse com um matcher Edit|Write é executado depois de qualquer ferramenta de edição de ficheiros. Guarde isto como .claude/hooks/after-edit.sh:
#!/bin/bash
# Format the edited file, then report lint failures back to the model.
INPUT=$(cat)
FILE=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')
[ -z "$FILE" ] && exit 0
case "$FILE" in
*.py)
ruff format "$FILE" >/dev/null 2>&1
if ! ruff check "$FILE" >&2; then
exit 2
fi
;;
*.sh)
if ! shellcheck "$FILE" >&2; then
exit 2
fi
;;
esac
exit 0{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/after-edit.sh",
"timeout": 60
}
]
}
]
}
}Peça ao Claude para adicionar uma função com indentação incorreta a um ficheiro Python e, em seguida, abra o ficheiro. O conteúdo aparece formatado. Essa é a confirmação de que o hook foi executado, porque um hook concluído com sucesso não mostra nada na conversa.
O exit 2 neste caso não desfaz nada. PostToolUse é executado depois de a ferramenta já ter sido executada, por isso a edição fica gravada no disco de qualquer forma. O que o exit 2 permite é que o resultado de ruff check seja enviado ao modelo como feedback. Assim, ele corrige o erro que acabou de introduzir em vez de continuar. Esta é a diferença entre uma falha de lint detetada no momento do commit e uma falha que o agente corrige no mesmo turno.
Há duas limitações dos matchers que são relevantes. Edit|Write não deteta ficheiros alterados por um comando de shell, e o Claude escreve ficheiros através de Bash com frequência suficiente para que essa lacuna seja real. Para obter cobertura em cada chamada, faça também o match de Bash e configure o script para listar os ficheiros alterados com git status --porcelain. Para obter cobertura uma vez por turno, coloque a verificação num hook Stop.
Exemplo 3: registar todas as chamadas de ferramentas para auditoria
Um matcher vazio em PostToolUse é acionado por todas as ferramentas. Enviar o registo para o journal do sistema, em vez de o gravar num ficheiro no diretório pessoal, impede que fique acessível à própria shell do agente:
{
"hooks": {
"PostToolUse": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "jq -c '{time: now|todate, session: .session_id, cwd: .cwd, tool: .tool_name, input: .tool_input}' | logger -t claude-code -p local0.info"
}
]
}
]
}
}Leia o registo com journalctl -t claude-code -o cat | tail -n 5. Deve ver uma linha JSON por chamada de ferramenta, com a mais recente no fim. Se não aparecer nada, o hook não foi executado. A secção de resolução de problemas abaixo aborda este caso.
Adicione o mesmo bloco em PostToolUseFailure para registar as chamadas que falharam, porque PostToolUse só é acionado em caso de sucesso e um comando falhado é normalmente o mais relevante. O motivo para usar logger, em vez de acrescentar dados a um ficheiro no diretório pessoal, é a propriedade do ficheiro: um hook é executado com o mesmo utilizador da shell do agente. Portanto, tudo o que esse utilizador pode abrir para acrescentar dados também pode truncar. O journal é escrito por systemd-journald com a sua própria conta.
Quanto tempo um hook pode executar
The data behind this chart
[
{
"label": "command, http or mcp_tool hook",
"default_timeout_seconds": 600
},
{
"label": "agent hook",
"default_timeout_seconds": 60
},
{
"label": "prompt hook",
"default_timeout_seconds": 30
},
{
"label": "command hook on UserPromptSubmit",
"default_timeout_seconds": 30
},
{
"label": "command hook on MessageDisplay",
"default_timeout_seconds": 10
},
{
"label": "any hook on SessionEnd",
"default_timeout_seconds": 1.5
}
]Por predefinição, um hook de comando tem 600 segundos para executar, ou seja, dez minutos. Alguns eventos reduzem esse limite de forma significativa. Os hooks SessionEnd partilham um orçamento de 1.5 segundos, que é aplicado ao conjunto de hooks. Por isso, a limpeza no fim da sessão tem de ser rápida. Definir um timeout mais longo no hook aumenta esse orçamento partilhado até ao mesmo valor, com um máximo de 60 segundos.
Um hook que atinge o seu tempo limite é cancelado e não produz nenhuma decisão. Para uma proteção PreToolUse, isso significa que o hook não bloqueia a operação. A chamada da ferramenta continua pelo fluxo normal de permissões. Por esse motivo, mantenha os scripts de proteção pequenos. Para tarefas lentas que não exigem espera, como enviar um log para outro local, defina "async": true. O hook é executado em segundo plano sem atrasar a chamada da ferramenta.
Hooks, ficheiros de regras, skills e servidores MCP
Quatro coisas são frequentemente confundidas porque todas alteram o comportamento de um agente. Apenas uma deixa de ser uma sugestão.
Um ficheiro de regras (CLAUDE.md ou um ficheiro em .claude/rules/) é texto carregado no contexto do modelo. Orienta o comportamento, mas não impõe nada. Numa conversa longa, com um diff grande e um pedido novo do utilizador, uma das suas linhas pode ser ignorada. Esse é o mecanismo normal por trás de agentes que ignoram as instruções que escreveu.
Uma skill é uma pasta com instruções e scripts que o modelo carrega quando considera que a skill é relevante. Essa decisão é a finalidade de uma skill e também o seu limite: o modelo continua a decidir. Pode ver os dois lados numa skill como Ponytail, que orienta um agente para a menor alteração que funciona, porque ela define como uma tarefa completa deve ser abordada de uma forma que nenhum hook conseguiria, mas apenas enquanto o modelo decidir carregá-la.
Um servidor MCP (model context protocol) fornece ao modelo novas ferramentas para chamar. Amplia aquilo a que o agente pode aceder. Não faz com que o agente use essas ferramentas e é um processo separado que tem de operar, o que constitui uma tarefa própria: consulte como executar servidores MCP num VPS.
Um hook é o único dos quatro que é executado sem uma decisão do modelo. Use um ficheiro de regras para uma preferência e uma skill para um procedimento que o modelo deve seguir quando se aplicar. Use um hook para o passo que tem de acontecer sempre ou para aquilo que nunca pode acontecer. A comparação mais aprofundada, incluindo os casos em que uma skill é melhor do que um ficheiro de regras, está em a comparação entre skills, MCP e ficheiros de regras.
Um plugin é uma forma de empacotamento, não um quinto mecanismo. Agrupa hooks e skills numa única unidade instalável. É assim que uma equipa distribui a mesma proteção por todas as máquinas: consulte como funcionam os plugins do Claude Code.
A decisão de segurança num VPS partilhado
Um hook é código acionado pelo agente e executado com o utilizador que iniciou o Claude Code. Herda o ambiente e as permissões de ficheiros desse utilizador. Num portátil, isto é uma questão de fluxo de trabalho. Num VPS onde um agente é executado sem supervisão, é uma questão de segurança com quatro aspetos práticos.
Um hook num repositório é código que não escreveu. .claude/settings.json é submetido ao repositório, por isso clonar um repositório e iniciar uma sessão dentro dele pode registar hooks incluídos no repositório. O Claude Code coloca os hooks do projeto atrás da caixa de diálogo de confiança da área de trabalho para essa pasta. Aceitar a confiança é, portanto, o momento em que decide executá-los. Leia primeiro o bloco hooks.
Um hook vê toda a entrada da ferramenta. Um hook de auditoria que regista tool_input escreve todos os argumentos de todos os comandos num ficheiro, incluindo qualquer token que esteja numa linha de comandos. Esse log precisa então da mesma proteção que o segredo, o que faz parte do problema mais amplo de manter os segredos fora do alcance de um agente de IA.
Um hook pode escrever no contexto do modelo. Tudo o que um hook SessionStart ou UserPromptSubmit escreve em stdout é adicionado à conversa. Um hook que encaminha texto de uma origem externa, de um sistema de acompanhamento de problemas ou de um ficheiro de log entrega texto não fiável ao modelo como se o tivesse escrito pessoalmente. Um hook que encaminha uma nota de outra sessão do Claude Code no mesmo VPS faz o mesmo. A saída de um agente não merece mais confiança do que a do sistema de acompanhamento de problemas. Trate esse stdout como entrada, não como saída.
O privilégio é o verdadeiro controlo. Execute o agente com um utilizador dedicado e sem privilégios, apenas com as regras sudo de que precisa. Um deny PreToolUse é útil e, por definição, funciona como melhor esforço: a referência diz o mesmo sobre o filtro if e indica que deve usar o sistema de permissões quando precisar de uma negação efetiva. As regras de permissões e a conta sob a qual o processo é executado são os elementos que se mantêm eficazes sob pressão.
Há uma propriedade que se aplica a todas as configurações. Os hooks PreToolUse são executados antes da verificação do modo de permissões em qualquer modo de permissões, por isso um hook que devolva deny bloqueia a ferramenta mesmo com bypassPermissions. Os hooks podem tornar mais restrito o que as regras de permissões permitem. Não podem tornar essas regras menos restritivas.
Por que meu hook não é executado?
Siga estas etapas pela ordem. Cada etapa identifica o sintoma que você verá.
- Execute
/hookse confirme se o hook aparece no evento esperado. Quando um hook não aparece no menu, normalmente há um erro de sintaxe JSON no ficheiro de configurações, porque vírgulas finais e comentários não são permitidos, ou o ficheiro não está numa das seis localizações acima. - Compare o matcher exatamente com o nome da ferramenta. Os matchers diferenciam maiúsculas de minúsculas, portanto
"bash"nunca corresponde à ferramentaBash. - Execute o script manualmente com uma entrada de exemplo, como no exemplo 1 acima. Um código de saída inesperado indica um erro no script. O Claude Code comunica isso como um erro do hook, não como uma decisão.
- Uma mensagem com o texto
jq: command not foundsignifica quejqnão está instalado nessa máquina. Umcommand not foundpara o seu próprio script significa que o caminho não foi resolvido; use${CLAUDE_PROJECT_DIR}ou um caminho absoluto. Se o script nunca for executado, provavelmente não tem permissão de execução. - O hook imprime JSON válido, mas nada acontece. Um hook em formato shell é executado através de
sh -c. Se o perfil da sua shell imprimir um banner, esse banner é acrescentado antes do JSON. A saída padrão deixa de começar por{, portanto o Claude Code interpreta todo o conteúdo como texto simples e ignora a decisão. Quando o processo termina com o código 0, nada é apresentado em nenhum local, exceto no log de depuração. Envolva qualquerechono seu perfil para que seja executado apenas em shells interativas. - Se o problema continuar, inicie a sessão com
claude --debug-file /tmp/claude.loge executetail -f /tmp/claude.lognum segundo terminal. O log de depuração regista quais hooks corresponderam, o código de saída devolvido por cada um e tudo o que escreveram na saída padrão e na saída de erro.
FAQ
Qual é a diferença entre um hook do Claude Code e uma instrução CLAUDE.md?
Uma instrução CLAUDE.md é texto no contexto do modelo. Por isso, compete pela atenção com a conversa e com o pedido atual, e o modelo pode ponderá-la em relação a ambos. Um hook é um comando shell que o Claude Code executa num ponto fixo do seu ciclo de vida. Por isso, é executado em todas as ocorrências do respetivo evento, independentemente do que o modelo decidiu. Use uma instrução para uma preferência. Use um hook para uma etapa que tem de acontecer sempre ou para uma ação que nunca pode acontecer.
Como impeço o Claude Code de executar um comando shell específico?
Registe um hook PreToolUse com um matcher Bash que leia o comando de .tool_input.command, escreva um motivo em stderr e termine com o código 2. O Claude Code cancela a chamada e mostra o seu motivo ao modelo. Isto acontece antes da verificação do modo de permissões, por isso a negação mantém-se mesmo no modo bypassPermissions. A correspondência baseada numa cadeia de comando é uma salvaguarda, não uma fronteira de segurança, porque o mesmo comando pode ser escrito de uma forma que o padrão não detete. Por isso, complemente-a com regras de permissões e com uma conta sem privilégios.
O meu hook imprime JSON válido, mas nada acontece. Porquê?
A causa mais comum é o perfil da shell. Um hook sem um campo args é executado através de sh -c, e alguns perfis imprimem um banner em cada shell. Esse conteúdo aparece em stdout antes do JSON. Como a saída já não começa por {, o Claude Code trata todo o conteúdo como texto simples e ignora a decisão. Com o código de saída 0, nada é apresentado na transcrição. Proteja qualquer echo no seu perfil com um teste de shell interativa. Depois, confirme a correção lendo o log de depuração em claude --debug-file /tmp/claude.log.
É seguro executar hooks do Claude Code num servidor partilhado?
Os hooks são executados como o utilizador que iniciou o Claude Code e têm as permissões de ficheiros desse utilizador. Portanto, um hook pode fazer tudo o que essa conta puder fazer. Duas práticas cobrem a maior parte do risco: execute o agente com uma conta dedicada sem privilégios e com uma política sudo restrita; antes de aceitar a caixa de diálogo de confiança na área de trabalho, leia o bloco hooks de qualquer repositório, porque os hooks do projeto são incluídos em .claude/settings.json. Defina "disableAllHooks": true no seu ficheiro de definições quando não quiser que nenhum deles seja executado.