🎣 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
🔔 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.
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
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
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
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
⚙️ 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
.claude/hooks/validate-commit.sh com o JSON acima via stdin
🚦 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".
🛡️ 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
- •
jqnã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.
📝 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
Próximo Módulo:
1.3 — Slash Commands e Skills — como criar atalhos reutilizáveis para tarefas frequentes