Configure ganchos de eventos para impor políticas, automatizar lint e injetar contexto — sem tocar no código do agente.
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.
Dispara ao iniciar uma nova sessão. Stdout é injetado como mensagem inicial no contexto. Ideal para carregar estado do projeto, branch atual, tarefas pendentes.
Dispara antes de cada ferramenta. Exit 0 = permite. Exit não-zero = bloqueia a ferramenta. Stderr com mensagem de erro retorna ao agente.
Dispara após cada ferramenta retornar. Stdout é injetado no contexto. Ideal para lint automático, auditoria, métricas de uso.
Dispara quando o agente encerra. Ideal para limpeza, notificações de conclusão, relatório de sessão, persistência de memória.
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.
{
"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"
}
]
}
]
}
}
Vale para todos os projetos. Bom para políticas pessoais: bloqueio de comandos destrutivos, preferências de lint.
Específico do projeto. Commitado no repositório — toda a equipe herda as políticas automaticamente.
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
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
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
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.
#!/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
| 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) |
A skill /update-config do Claude Code facilita adicionar hooks ao settings.json via prompt natural: "adicione um hook de lint para TypeScript".
4.3 — ⌨️ Slash commands e workflows determinísticos