SSD Nodes Learn 🎉 VPS desde $5.50/mês
Guias Matt ConnorPor Matt Connor · Atualizado 2026-08-15

Hooks do Claude Code: eventos, regras e código 2

Entenda onde configurar hooks do Claude Code, quais eventos são acionados e como o código de saída 2 bloqueia ferramentas e envia stderr ao modelo.

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 é toda a diferença entre um hook e um ficheiro de regras. Uma instrução em CLAUDE.md é uma recomendação, e o modelo pondera-a com tudo o que existe no respetivo contexto. Um hook é código e é executado independentemente da decisão do modelo. Se o seu agente continua a ignorar o formatador que já lhe indicou duas vezes, não precisa de uma instrução mais explícita. Precisa de um hook.

O mecanismo é simples. Regista um comando num ficheiro de definições, associado ao nome de um evento. Quando esse evento ocorre, o Claude Code executa o comando e escreve os dados do evento na respetiva entrada padrão (stdin), em JSON (JavaScript object notation). O comando lê esses dados, executa o trabalho e devolve um estado de saída. O estado de saída 2 de um hook PreToolUse cancela a chamada da ferramenta antes da execução, e tudo o que o script escreveu 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 release 2.1.232. Esta interface muda rapidamente. Consulte a referência correspondente à sua versão antes de copiar JSON de qualquer publicação, incluindo esta. Imprima a sua com claude --version.

Onde fica a configuração dos hooks

Um hook é um bloco JSON num ficheiro de definições. Seis localizações 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 todos os que o clonarem recebam o hook.
  • .claude/settings.local.json: um projeto, apenas na sua máquina.
  • Definições de política gerida: abrangem toda a organização e são configuradas por um administrador.
  • hooks/hooks.json dentro de um plugin: ficam ativas enquanto esse plugin estiver ativado.
  • Frontmatter de uma skill ou de um subagente: fica 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 dentro de uma sessão para listar todos os hooks atualmente 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 monitor de ficheiros deteta a edição sem ser necessário reiniciar.

Quais eventos de hooks existem no Claude Code

A versão 2.1.232 lista trinta e um eventos, desde SessionStart até SessionEnd, abrangendo compactação, subagentes, worktrees e ficheiros de configuração. Para trabalho de administração de servidores, alguns deles são os mais utilizados.

  • PreToolUse: antes de uma chamada de ferramenta ser executada. É o único que pode bloquear a execução.
  • PostToolUse: depois de uma chamada de ferramenta ser concluída com sucesso. PostToolUseFailure é acionado quando ela falha. Por isso, um hook que precise de ver todos os resultados deve usar ambos.
  • PermissionRequest: quando uma chamada de ferramenta precisa de uma decisão de permissão. É nesse momento que o pedido de aprovação seria apresentado.
  • UserPromptSubmit: quando envia um prompt, antes de o Claude o processar. Tudo o que este hook escrever em stdout será adicionado ao contexto do modelo.
  • SessionStart e SessionEnd: no início e no fim de cada sessão. SessionStart também é acionado depois da compactação, com o valor compact no matcher.
  • Stop: quando o Claude termina de responder. O evento ocorre uma vez por turno, não uma vez por tarefa concluída.

Cada grupo inclui um matcher que determina quais ocorrências executam o hook. Nos eventos de ferramentas, ele filtra pelo nome da ferramenta. Assim, "Edit|Write" é acionado em edições de ficheiros e em mais nada. Os matchers distinguem maiúsculas de minúsculas. Um matcher vazio é acionado em todas as ocorrências. As ferramentas de um servidor MCP (model context protocol) têm nomes no formato mcp__<server>__<tool>. Por isso, um matcher "mcp__github__.*" seleciona as ferramentas de um servidor e ignora as dos restantes.

Os hooks Stop têm uma particularidade importante que deve conhecer antes de criar um. Um hook Stop que bloqueia a execução envia o modelo de volta ao trabalho, 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 o 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 acrescentam tool_name, tool_input e tool_use_id. Os outros eventos contêm os seus próprios campos: UserPromptSubmit recebe o texto de prompt, e SessionStart recebe um source de startup, resume, clear, compact ou fork.

jq é a forma habitual de ler estes dados num script shell, e uma imagem de servidor mínima 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 hook não apresenta objeções. Em PreToolUse, isso não equivale a uma aprovação, e o fluxo normal de permissões continua. Em UserPromptSubmit e SessionStart, 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, como PostToolUse, o bloqueio é ignorado, embora o stderr continue a chegar ao modelo como feedback.
  • Qualquer outro código de saída é um erro que não bloqueia a ação. A ação prossegue. 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 escreva 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 pedido de confirmação interativo, "deny" cancela a chamada e envia o motivo ao modelo, e "ask" mostra o pedido 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 então as respostas e mantém a mais restritiva, pela seguinte ordem: deny, defer, ask, allow.

Exemplo 1: bloquear um comando destrutivo antes da execução

Guarde isto 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 0

Torne o ficheiro executável e registe-o depois 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 por omissão:

echo '{"tool_name":"Bash","tool_input":{"command":"rm -rf /var/lib/postgresql"}}' \
  | .claude/hooks/block-destructive.sh
echo $?

Deverá 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 deverá ver um código de saída 0 sem qualquer saída. Numa sessão, a chamada bloqueada aparece na transcrição com a sua mensagem como motivo, e o modelo lê essa mensagem e adapta o comportamento.

Há uma propriedade que 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 o efeito mesmo com bypassPermissions. Isto torna um hook útil em conjunto com o modo automático e as definições de permissões do Claude Code, em que os pedidos de confirmação são reduzidos, mas o hook continua a ser executado.

Seja claro sobre o que isto faz. A correspondência de padrões numa cadeia de comando é uma proteção contra a execução descuidada por parte de um agente. Não é uma barreira 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 sob a qual o processo é executado.

Exemplo 2: formatar e verificar o código 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 terminado, por isso a edição fica no disco de qualquer forma. O que o exit 2 permite é que o resultado de ruff check chegue ao modelo como feedback, para que este corrija o erro que acabou de introduzir em vez de continuar. Essa é 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 importantes dos matchers. Edit|Write não deteta ficheiros alterados por um comando shell, e o Claude escreve ficheiros através de Bash com frequência suficiente para que essa lacuna seja relevante. 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 cada chamada de ferramenta para auditoria

Um matcher vazio em PostToolUse é acionado para todas as ferramentas. Enviar o registo para o journal do sistema, em vez de o gravar num ficheiro no diretório pessoal, mantém-no fora do alcance da 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 novamente com journalctl -t claude-code -o cat | tail -n 5. Deverá 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 esse caso.

Adicione o mesmo bloco em PostToolUseFailure para registar chamadas que falharam, porque PostToolUse só é acionado em caso de sucesso e um comando que falhou é normalmente o mais relevante. O motivo para usar logger, em vez de acrescentar dados a um ficheiro no diretório pessoal, é a propriedade: um hook é executado com o mesmo utilizador da shell do agente. Assim, 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.

Por quanto tempo um hook pode executar

ChartDefault hook timeout in seconds, by hook type and event
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 limite de 1.5 segundos entre todos eles. Por isso, a limpeza no fim da sessão tem de ser rápida. Definir um timeout mais longo no hook aumenta esse limite partilhado até ao mesmo valor, com um máximo de 60 segundos.

Um hook que atinge o tempo limite é cancelado e não produz nenhuma decisão. Num mecanismo de proteção PreToolUse, isso significa que o hook não bloqueia a operação. A chamada da ferramenta prossegue no fluxo normal de permissões. Por esse motivo, mantenha os scripts de proteção pequenos. Para tarefas demoradas que não exigem uma resposta imediata, como enviar um log para outro local, defina "async": true. Nesse caso, o hook é executado em segundo plano sem atrasar a chamada da ferramenta.

Hooks, ficheiros de regras, skills e servidores MCP

Há quatro coisas que se confundem 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. Esse julgamento é o objetivo 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 alteração mínima que funciona, porque ela define como uma tarefa completa é 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. Alarga 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 executar servidores MCP num VPS.

Um hook é o único dos quatro que é executado sem o modelo o escolher. Use um ficheiro de regras para uma preferência e uma skill para um procedimento que o modelo deve seguir quando aplicável. Use um hook para o passo que tem de ocorrer sempre ou para aquilo que nunca pode acontecer. A comparação mais detalhada, incluindo quando 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 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 como o utilizador que iniciou o Claude Code. Ele 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 do workspace para essa pasta. Aceitar a confiança é 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 imprime em stdout é adicionado à conversa. Um hook que encaminha texto de uma fonte externa, de um sistema de acompanhamento de problemas ou de um ficheiro de log entrega texto não confiável ao modelo como se o tivesse introduzido pessoalmente. Trate esse stdout como entrada, não como saída.

O privilégio é o controlo real. Execute o agente como um utilizador dedicado sem privilégios, com apenas as regras sudo necessárias. Um deny PreToolUse é útil e, por definição, funciona apenas como melhor esforço: a documentação de referência diz o mesmo sobre o filtro if e recomenda usar o sistema de permissões quando precisar de uma recusa efetiva. As regras de permissões e a conta sob a qual o processo é executado são os elementos que continuam válidos sob pressão.

Uma propriedade aplica-se a todas as configurações. Os hooks PreToolUse são executados antes da verificação do modo de permissões em todos os modos de permissões. Por isso, um hook que devolva deny bloqueia a ferramenta, mesmo com bypassPermissions. Os hooks podem restringir o que as regras de permissões permitem. Não podem alargar essas permissões.

Por que o meu hook não é executado?

Siga estes passos pela ordem. Cada passo identifica o sintoma que verá.

  • Execute /hooks e verifique se o hook aparece no evento esperado. Se um hook não aparecer no menu, normalmente o ficheiro de definições tem um erro de sintaxe JSON, porque não são permitidas vírgulas finais nem comentários, ou o ficheiro não está numa das seis localizações anteriores.
  • Compare o matcher exatamente com o nome da ferramenta. Os matchers diferenciam maiúsculas de minúsculas, por isso "bash" nunca corresponde à ferramenta Bash.
  • Execute o script manualmente com dados de entrada de exemplo, como no exemplo 1 anterior. Um código de saída inesperado indica um erro no script. O Claude Code comunica-o como um erro do hook, e não como uma decisão.
  • Uma mensagem com o texto jq: command not found significa que jq está em falta nessa máquina. Um command not found para 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 no formato shell é executado através de sh -c. Se o perfil da sua shell imprimir um banner, esse texto é colocado antes do JSON. O stdout deixa de começar por {. Assim, o Claude Code interpreta todo o conteúdo como texto simples e ignora a decisão. Com o código de saída 0, nada é comunicado noutro local além do log de depuração. Envolva qualquer echo no seu perfil para que seja executado apenas em shells interativas.
  • Se o problema persistir, inicie a sessão com claude --debug-file /tmp/claude.log e execute tail -f /tmp/claude.log num 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 no stdout e no stderr.

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 da decisão do modelo. Use uma instrução para uma preferência. Use um hook para uma etapa que tem de ocorrer sempre ou para uma ação que nunca pode ocorrer.

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 o 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 com um padrão numa cadeia de comando é uma proteção, não uma fronteira de segurança, porque o mesmo comando pode ser escrito de uma forma que o padrão não detete. Reforce-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 todas as shells. Esse texto 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 a saída 0, não é apresentada qualquer informação na transcrição. Proteja qualquer echo no seu perfil com um teste para 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 com o utilizador que iniciou o Claude Code e com as permissões de ficheiros desse utilizador. Por isso, 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; leia também o bloco hooks de qualquer repositório antes de aceitar a caixa de diálogo de confiança da área de trabalho, porque os hooks do projeto são fornecidos em .claude/settings.json. Defina "disableAllHooks": true no seu ficheiro de definições quando não quiser executar nenhum deles.