MÓDULO 1.2

🎣 Hooks: o Sistema de Eventos do Claude Code

📚

Tópicos

6

⏱️

Minutos

45

🎯

Nível

Iniciante

⚙️

Tipo

Técnico

1

🎣 O que é um hook e por que ele existe

Um hook é um ponto de extensão no loop do Claude Code. Em vez de modificar o comportamento do modelo em si, hooks permitem que scripts externos observem e controlem o que o Claude faz — sem substituir sua inteligência, apenas interceptando suas ações.

🔌 O papel do hook no sistema

Pense em hooks como guardas de segurança em uma fábrica. O Claude Code é o engenheiro que decide o que fazer — os hooks são os protocolos de segurança que ele deve respeitar antes de executar certas ações.

  • Observar: ver tudo que o Claude faz (PostToolUse) sem interferir
  • Validar: verificar se uma ação está de acordo com as regras (PreToolUse)
  • Bloquear: impedir ações que violam políticas de segurança
  • Notificar: enviar alertas quando eventos importantes acontecem

✓ Casos de uso reais

  • • Bloquear commits sem mensagem descritiva
  • • Logar todas as escritas de arquivo em auditoria
  • • Notificar Slack quando Claude finaliza uma tarefa
  • • Validar que código segue padrões antes de salvar
  • • Impedir deleção de arquivos em diretórios críticos

✗ O que hooks NÃO fazem

  • • Não modificam o que o Claude pensa
  • • Não têm acesso ao histórico de conversa
  • • Não podem modificar o output da ferramenta (PostToolUse)
  • • Não substituem CLAUDE.md para instruções persistentes
  • • Não são adequados para lógica de negócio complexa
2

🔔 Os 4 tipos de hook

O Claude Code oferece quatro tipos de hook, cada um disparado em um momento diferente do loop. Escolher o tipo errado é o erro mais comum ao escrever hooks — apenas PreToolUse pode bloquear ações.

PreToolUse

Executado antes da ferramenta rodar. É o único tipo que pode bloquear a execução com exit code 1. Recebe o nome da ferramenta e seus parâmetros.

Uso: validação, bloqueio de ações perigosas, auditoria preventiva

PostToolUse

Executado após a ferramenta rodar. Não pode bloquear retroativamente. Recebe o resultado da execução. Ideal para logging e notificações.

Uso: auditoria de ações executadas, logs, gatilhos pós-ação

Stop

Executado quando o Claude finaliza uma resposta ou tarefa. Não recebe dados de ferramenta. Ideal para cleanup, resumos e notificações de conclusão.

Uso: notificações de conclusão, limpeza de estado temporário, relatórios

Notification

Executado para eventos assíncronos e alertas do sistema. Menos comum que os outros — usado para integração com sistemas externos de monitoramento.

Uso: integrações com Slack, PagerDuty, sistemas de monitoramento

3

⚙️ Como o Claude Code executa um hook

O mecanismo de execução é simples e robusto: o Claude Code abre um subprocess com o comando do hook, passa um JSON via stdin com o contexto do evento e lê o resultado para tomar decisões.

📊 O payload JSON de um PreToolUse Bash

{
  "event": "PreToolUse",
  "tool": {
    "name": "Bash",
    "input": {
      "command": "git commit -m 'fix bug'",
      "description": "Commiting the bugfix"
    }
  },
  "session_id": "sess_abc123",
  "cwd": "/home/user/my-project"
}

🔄 O fluxo completo de execução

1.Claude decide usar a ferramenta Bash com o comando "git commit -m 'fix bug'"
2.Claude Code verifica se há hooks PreToolUse registrados para Bash no settings.json
3.Abre subprocess: .claude/hooks/validate-commit.sh com o JSON acima via stdin
4.Aguarda o script terminar (com timeout configurável)
5.Lê o exit code: 0 = continua, 1 = bloqueia e passa stderr para o Claude
4

🚦 Retorno de exit code: BLOCK vs APPROVE

O protocolo de comunicação entre o hook e o Claude Code é simples e deliberadamente minimalista: exit code 0 aprova, exit code 1 bloqueia. A mensagem de bloqueio vai via stderr e é vista pelo Claude como contexto de erro.

✓ Exit 0 — APPROVE

#!/bin/bash
# Validação passou
echo "Commit aprovado" >&2
exit 0  # Ferramenta executa

✗ Exit 1 — BLOCK

#!/bin/bash
# Mensagem de erro para o Claude
echo "ERRO: Mensagem muito curta.
Use pelo menos 20 caracteres." >&2
exit 1  # Ferramenta bloqueada

💡 O Claude lê o stderr

Quando o hook retorna exit 1, o conteúdo do stderr é injetado no contexto do Claude como mensagem de erro. Isso significa que você pode fornecer instruções no stderr para guiar o Claude a corrigir a ação — não apenas dizer "bloqueado".

5

🛡️ Fail-open: quando o hook falha

O Claude Code implementa fail-open por design: se um hook falhar por qualquer motivo — erro de sintaxe, timeout, jq não instalado, exceção não tratada — o Claude Code ignora o hook e continua. A ferramenta executa como se o hook não existisse.

🤔 Por que fail-open e não fail-closed?

Fail-closed (bloquear quando o hook falha) parece mais seguro, mas criaria um problema grave: qualquer bug no hook travaria completamente o Claude Code. Em ambientes de desenvolvimento, isso seria inaceitável.

A escolha de fail-open significa que hooks são um caminho adicional de segurança, não o único. Para garantias críticas, você precisa de outros controles além dos hooks.

⚠️ Cenários de fail-open

  • jq não está instalado no ambiente do hook
  • O script do hook não tem permissão de execução (chmod +x esquecido)
  • O hook demora mais que o timeout configurado
  • Erro de sintaxe no shell script (bash falha ao iniciar)
  • O arquivo do hook não existe no caminho especificado no settings.json

💡 Como detectar falhas silenciosas

Sempre adicione logging no início do hook: echo "[$(date)] Hook iniciado" >> .claude/logs/hooks.log. Se o log não aparecer quando esperado, o hook está falhando antes de executar.

6

📝 Escrevendo seu primeiro hook em shell script

Um hook mínimo funcional em bash: lê o JSON do stdin, extrai o campo relevante com jq, aplica a lógica e retorna o exit code correto. Abaixo, um exemplo completo de um hook que bloqueia comandos rm -rf sem confirmação.

💻 Hook completo: bloquear rm -rf

#!/bin/bash
# .claude/hooks/block-rm-rf.sh
# Hook PreToolUse que bloqueia rm -rf

set -euo pipefail

LOG_FILE=".claude/logs/hooks.log"
mkdir -p "$(dirname "$LOG_FILE")"

# Lê o payload JSON do stdin
PAYLOAD=$(cat)

# Log de início
echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] block-rm-rf: iniciado" >> "$LOG_FILE"

# Extrai o nome da ferramenta e o comando
TOOL_NAME=$(echo "$PAYLOAD" | jq -r '.tool.name // empty')
COMMAND=$(echo "$PAYLOAD" | jq -r '.tool.input.command // empty')

# Só processa chamadas ao Bash
if [ "$TOOL_NAME" != "Bash" ]; then
  exit 0
fi

# Verifica se é um rm -rf
if echo "$COMMAND" | grep -qE 'rm\s+-[a-z]*r[a-z]*f|rm\s+-rf'; then
  echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] block-rm-rf: BLOQUEADO: $COMMAND" >> "$LOG_FILE"
  echo "HOOK BLOQUEOU: 'rm -rf' detectado.
Use 'rm -r' sem -f, ou confirme explicitamente que deseja deletar com força.
Comando: $COMMAND" >&2
  exit 1
fi

echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] block-rm-rf: aprovado" >> "$LOG_FILE"
exit 0

⚙️ Registrando no settings.json

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": ".claude/hooks/block-rm-rf.sh"
          }
        ]
      }
    ]
  }
}

💡 Checklist antes de ativar

  • chmod +x .claude/hooks/block-rm-rf.sh — permissão de execução
  • echo '{"tool":{"name":"Bash","input":{"command":"rm -rf /"}}}' | ./block-rm-rf.sh — teste manual
  • Verifica o exit code: echo $? deve retornar 1 para o comando bloqueado
  • Verifica que o log foi criado em .claude/logs/hooks.log

Resumo do Módulo 1.2

Hooks interceptam, não substituem — eles estendem o Claude Code sem alterar sua inteligência
4 tipos: PreToolUse, PostToolUse, Stop, Notification — só PreToolUse bloqueia
Protocolo: stdin JSON → lógica → exit code — 0 aprova, 1 bloqueia com stderr
Fail-open por design — hooks que falham não travam o Claude Code
Estrutura de um hook mínimo — cat stdin, jq parse, lógica, exit 0 ou 1

Próximo Módulo:

1.3 — Slash Commands e Skills — como criar atalhos reutilizáveis para tarefas frequentes