4.2 ~50 min · Intermediário

🪝 Hooks: automatizar o harness

Configure ganchos de eventos para impor políticas, automatizar lint e injetar contexto — sem tocar no código do agente.

SessionStart injeta contexto PreToolUse valida/bloqueia ⚙️ Ferramenta Bash · Read · Write executa aqui PostToolUse lint · auditoria Stop cleanup hook.sh lint.sh Ciclo de vida do harness — hooks interceptam em 4 pontos

🪝 O que são hooks

Hooks são scripts externos executados pelo harness em momentos específicos do ciclo de vida do agente. Eles recebem metadados do evento via stdin e podem influenciar o comportamento do harness através do exit code e output.

A grande vantagem: você adiciona comportamento sem modificar o prompt ou o agente. As políticas ficam em scripts versionados no repositório, aplicadas automaticamente para toda a equipe.

🔔 SessionStart

Dispara ao iniciar uma nova sessão. Stdout é injetado como mensagem inicial no contexto. Ideal para carregar estado do projeto, branch atual, tarefas pendentes.

🔍 PreToolUse

Dispara antes de cada ferramenta. Exit 0 = permite. Exit não-zero = bloqueia a ferramenta. Stderr com mensagem de erro retorna ao agente.

📋 PostToolUse

Dispara após cada ferramenta retornar. Stdout é injetado no contexto. Ideal para lint automático, auditoria, métricas de uso.

🛑 Stop

Dispara quando o agente encerra. Ideal para limpeza, notificações de conclusão, relatório de sessão, persistência de memória.

⚙️ Configurar em settings.json

Hooks são configurados na chave hooks do settings.json. Cada entrada define: o evento, um matcher de ferramenta (opcional), e o comando shell a executar.

// .claude/settings.json — exemplo com 4 hooks
{
  "hooks": {
    "SessionStart": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "bash .claude/hooks/session-start.sh"
          }
        ]
      }
    ],
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "bash .claude/hooks/block-dangerous.sh"
          }
        ]
      }
    ],
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "command",
            "command": "bash .claude/hooks/lint.sh"
          }
        ]
      }
    ],
    "Stop": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "bash .claude/hooks/session-end.sh"
          }
        ]
      }
    ]
  }
}

💡 Global vs Local

~/.claude/settings.json

Vale para todos os projetos. Bom para políticas pessoais: bloqueio de comandos destrutivos, preferências de lint.

.claude/settings.json

Específico do projeto. Commitado no repositório — toda a equipe herda as políticas automaticamente.

🛠️ Casos de uso práticos

🧹

Lint automático após cada save

PostToolUse em Write/Edit roda eslint/prettier. O output é injetado no contexto — o agente vê os erros e corrige automaticamente.

#!/bin/bash # .claude/hooks/lint.sh
FILE=$(cat /dev/stdin | jq -r '.tool_input.file_path // empty')
if [[ "$FILE" == *.ts || "$FILE" == *.tsx ]]; then
  npx eslint --fix "$FILE" 2>&1
fi
🔒

Bloquear comandos perigosos

PreToolUse em Bash inspeciona o comando. Se detectar padrões proibidos, retorna exit 1 com mensagem explicando o bloqueio.

#!/bin/bash # .claude/hooks/block-dangerous.sh
CMD=$(cat /dev/stdin | jq -r '.tool_input.command // empty')
DANGEROUS="rm -rf|git push --force|DROP TABLE|truncate"
if echo "$CMD" | grep -qE "$DANGEROUS"; then
  echo "❌ Comando bloqueado por política de segurança" >&2
  exit 1
fi
💉

Injetar contexto no SessionStart

Ao iniciar cada sessão, injeta automaticamente o estado atual: branch, commits recentes, issues abertas. O agente começa já contextualizado.

#!/bin/bash # .claude/hooks/session-start.sh
echo "=== CONTEXTO DO PROJETO ==="
echo "Branch: $(git branch --show-current)"
echo "Último commit: $(git log -1 --oneline)"
echo "Arquivos modificados:"
git status --short | head -10

⚖️ O que fazer e o que evitar

✓ Boas práticas

  • Scripts rápidos — hooks com timeout curto não travam o agente
  • Mensagens claras no stderr quando bloquear
  • Usar matcher para aplicar só onde faz sentido
  • Versionar scripts em .claude/hooks/

✗ Armadilhas comuns

  • Hooks que demoram > 5s — travam a execução do agente
  • PreToolUse em todas as ferramentas sem matcher específico
  • Injetar output muito grande no SessionStart (context pressure)
  • Confiar só em fail-open para hooks de segurança críticos

🛡️ Segurança e fail-open

Fail-open é o comportamento padrão: se um hook falha (timeout, erro de script, crash), o harness continua a execução normalmente. A ferramenta que o hook deveria interceptar não é bloqueada.

Isso é intencional para hooks de enriquecimento (lint, contexto) — você não quer que um lint quebrado interrompa todo o trabalho. Mas para hooks de segurança, fail-open é perigoso.

🔑 Regra prática

Hooks de auditoria: fail-open está ok. Se o log falhar, o trabalho continua.
Hooks de segurança: torne o script extremamente simples e rápido — o risco de falha cai a zero.
// Boas práticas de robustez em hooks
#!/bin/bash
# Sempre: timeout explícito para não travar o agente
# Leitura do stdin com timeout
INPUT=$(timeout 2 cat /dev/stdin 2>/dev/null || echo '{}')

# Parse seguro com fallback
CMD=$(echo "$INPUT" | jq -r '.tool_input.command // empty' 2>/dev/null)

# Se nada para verificar, sair com sucesso (fail-open explícito)
[ -z "$CMD" ] && exit 0

# Verificação principal
if echo "$CMD" | grep -qE "rm -rf /|DROP DATABASE"; then
  echo "Comando bloqueado: risco de perda de dados irreversível" >&2
  exit 1
fi

exit 0

📊 Dados disponíveis em cada hook

Evento Dados no stdin Impacto do output
SessionStart Metadados da sessão Stdout injetado como mensagem inicial
PreToolUse Nome da tool + tool_input (JSON) Exit 0=permite, 1=bloqueia; stderr→agente
PostToolUse tool_input + tool_response (JSON) Stdout injetado no contexto atual
Stop Razão do encerramento Apenas side-effects (log, notificação)

Hooks mais usados

  1. 1. PostToolUse Write → lint automático
  2. 2. PreToolUse Bash → bloqueio de comandos
  3. 3. SessionStart → injeção de contexto
  4. 4. Stop → notificação de conclusão

🔧 Skill /update-config

A skill /update-config do Claude Code facilita adicionar hooks ao settings.json via prompt natural: "adicione um hook de lint para TypeScript".

🎯 Resumo do Módulo 4.2

4 eventos — SessionStart, PreToolUse, PostToolUse, Stop
settings.json — configuração declarativa, versionável, herdada pela equipe
Lint automático — PostToolUse em Write/Edit, output injetado no contexto
Bloqueio — PreToolUse com exit 1 + mensagem no stderr
Fail-open — hook que falha não bloqueia execução por padrão

Próximo Módulo:

4.3 — ⌨️ Slash commands e workflows determinísticos