Guia de Hooks
Hooks do Claude Code do princípio ao fim: exit codes, output JSON, comandos assíncronos, endpoints HTTP, matchers PreToolUse e PostToolUse, padrões para produção.
Pare de configurar. Comece a construir.
Templates SaaS com orquestração de IA.
Problema: Você aprova cada escrita de ficheiro à mão. Depois cada comando. Depois cada passe de formatação. Vinte interrupções depois, a funcionalidade que estavas a construir saiu da tua cabeça.
Solução Rápida: Adiciona isto ao .claude/settings.json e nunca mais aproves um Prettier format:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "npx prettier --write \"$CLAUDE_TOOL_INPUT_FILE_PATH\""
}
]
}
]
}
}Cada ficheiro que Claude escreve fica automaticamente formatado. Zero cliques. Zero troca de contexto.
Os 12 Eventos do Ciclo de Vida dos Hooks
Cada evento do Claude Code pode disparar um hook. Um hook executa um comando shell, ou envia um prompt LLM, quando esse evento acontece. A lista completa:
| Hook | Quando Dispara | Pode Bloquear? | Melhor Uso |
|---|---|---|---|
| SessionStart | Sessão começa ou retoma | NÃO | Carregar contexto, definir env vars |
| UserPromptSubmit | Carregas enter | SIM | Injeção de contexto, validação |
| PreToolUse | Antes da ferramenta correr | SIM | Bloqueio de segurança, auto-aprovação (estende o sistema de permissões) |
| PermissionRequest | Diálogo de permissão aparece | SIM | Auto-aprovação/recusa |
| PostToolUse | Depois da ferramenta ter sucesso | NÃO* | Auto-format, lint, log |
| PostToolUseFailure | Depois da ferramenta falhar | NÃO | Tratamento de erros |
| SubagentStart | A lançar subagente | NÃO | Inicialização do subagente |
| SubagentStop | Subagente termina | SIM | Validação do subagente |
| Stop | Claude termina de responder | SIM | Aplicação de tarefas |
| PreCompact | Antes da compactação | NÃO | Backup do transcript |
| Setup | Com --init/--maintenance | NÃO | Configuração única |
| SessionEnd | Sessão termina | NÃO | Limpeza, logging |
| Notification | Claude envia notificação | NÃO | Alertas no desktop, TTS |
*Um hook PostToolUse pode enviar uma mensagem de volta ao Claude, mas não pode desfazer a ferramenta que já correu.
Exit Codes: O Mecanismo de Controlo
Os exit codes são como um hook comunica com Claude:
| Exit Code | O Que Acontece |
|---|---|
| 0 | Sucesso - hook correu, stdout processado para JSON |
| 2 | Bloqueio - operação parada, stderr enviado ao Claude |
| Outro | Erro - stderr mostrado ao utilizador, execução continua |
O exit code 2 é o que bloqueia as coisas. Devolve-o de um hook PreToolUse e a ferramenta nunca corre. Devolve-o de um hook Stop e Claude tem de continuar em vez de terminar.
Tipos de Hook: Command, HTTP, Prompt e Agent
Existem quatro tipos de handler. Escolhe o que se encaixa melhor no trabalho.
Hooks de comando correm scripts shell:
{
"type": "command",
"command": "python validator.py",
"timeout": 30
}Hooks HTTP fazem POST para um endpoint e recebem JSON de volta: Novo - Fev 2026
{
"type": "http",
"url": "http://localhost:8080/hooks/pre-tool-use",
"timeout": 30,
"headers": {
"Authorization": "Bearer $MY_TOKEN"
},
"allowedEnvVars": ["MY_TOKEN"]
}O Claude Code envia o evento JSON como corpo do pedido (Content-Type: application/json), e a resposta segue o mesmo esquema JSON de output que um hook de comando usaria. Algumas diferenças:
- Erros não bloqueantes: Uma resposta não-2xx, uma falha de ligação ou um timeout não param a execução. Para bloquear mesmo uma chamada de ferramenta, devolve uma resposta 2xx com
decision: "block"no corpo JSON. - Env vars nos headers: Referencia variáveis com
$VAR_NAMEou${VAR_NAME}dentro dos valores de header. Só os nomes listados emallowedEnvVarssão resolvidos. Qualquer outro fica como string vazia. - Deduplicação: Hooks HTTP deduplicam por URL. Hooks de comando deduplicam por string de comando.
- Só via config: Tens de editar o JSON de settings diretamente. O menu interativo
/hookssó suporta hooks de comando.
Hooks de prompt usam avaliação LLM (boa escolha para Stop e SubagentStop):
{
"type": "prompt",
"prompt": "Evaluate if Claude should stop: $ARGUMENTS. Check if all tasks complete.",
"timeout": 30
}O LLM responde com {"ok": true} ou {"ok": false, "reason": "..."}.
Hooks de agente lançam um subagente que pode ler ficheiros (Read, Grep, Glob) para uma verificação mais profunda:
{
"type": "agent",
"prompt": "Verify all test files have corresponding implementation files",
"timeout": 60
}Um hook de agente explora o codebase primeiro e depois decide. Isso torna-o mais completo que um hook de prompt. Também é mais lento (timeout padrão: 60s vs 30s para prompts).
Hooks Assíncronos (Não Bloqueantes) Novo - Jan 2026
Define async: true e o hook corre em segundo plano em vez de bloquear Claude. A Anthropic lançou isto em janeiro de 2026:
{
"type": "command",
"command": "node backup-script.js",
"async": true,
"timeout": 30
}Ideal para:
- Logging e analytics
- Criação de backups (PreCompact)
- Notificações
- Qualquer efeito colateral que não deva atrasar as coisas
Não adequado para:
- Bloqueio de segurança (PreToolUse com exit code 2)
- Decisões de auto-aprovação (PermissionRequest)
- Qualquer hook onde Claude precise do resultado
Hooks HTTP: POST para Endpoints Novo - Fev 2026
Aponta um hook HTTP para um URL web que controlas e o evento flui para lá em vez de ir para um script local. Isso abre um conjunto de padrões que os hooks de comando não conseguiam lidar bem:
- Serviços de validação remotos que aplicam políticas de equipa
- Logging centralizado para um sistema de auditoria partilhado
- Integrações webhook com Slack, PagerDuty ou dashboards personalizados
- Arquiteturas de microsserviços onde a lógica dos hooks corre junto à tua API
Hook HTTP Básico
Encaminha todos os comandos Bash por um endpoint de validação:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "http",
"url": "http://localhost:8080/hooks/pre-tool-use",
"timeout": 30,
"headers": {
"Authorization": "Bearer $MY_TOKEN"
},
"allowedEnvVars": ["MY_TOKEN"]
}
]
}
]
}
}O que chega ao teu servidor é o payload stdin exato que um hook de comando leria, entregue pelo corpo do POST. Responde no formato JSON de output e o Claude Code analisa-o da mesma forma.
Tratamento de Respostas HTTP
Os hooks HTTP leem respostas de forma diferente. Sem exit codes. Apenas status HTTP e corpo da resposta:
| Resposta | Comportamento |
|---|---|
| 2xx + corpo vazio | Sucesso, equivalente a exit code 0 sem output |
| 2xx + corpo de texto simples | Sucesso, o texto é adicionado como contexto ao Claude |
| 2xx + corpo JSON | Sucesso, analisado usando o mesmo esquema JSON que hooks de comando |
| Status não-2xx | Erro não bloqueante, execução continua |
| Falha de ligação / timeout | Erro não bloqueante, execução continua |
O ponto a ter em atenção: Um hook HTTP não pode bloquear só com status codes. Envia um 4xx ou 5xx e obtens um erro registado enquanto a execução continua. O bloqueio real, ou uma recusa real de permissão, precisa de uma resposta 2xx cujo corpo JSON inclua os campos de decisão:
{
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "deny",
"permissionDecisionReason": "Blocked by security policy"
}
}Autenticação Segura com Headers
O campo headers pode interpolar variáveis de ambiente, mas só quando a variável aparece em allowedEnvVars. Isso evita que segredos vazem por acidente:
{
"type": "http",
"url": "https://hooks.example.com/validate",
"headers": {
"Authorization": "Bearer $API_KEY",
"X-Team-Id": "$TEAM_ID"
},
"allowedEnvVars": ["API_KEY", "TEAM_ID"]
}Um $VAR que não está na allowlist torna-se silenciosamente uma string vazia. Sem aviso. Sem erro. Só vazio. Revê a allowlist duas vezes antes de fazer deploy.
Output JSON: Controlo Avançado
Quando um exit code é demasiado básico, um hook pode devolver uma resposta JSON estruturada.
Decisões PreToolUse
{
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "allow",
"permissionDecisionReason": "Safe read operation",
"updatedInput": { "command": "modified-command" },
"additionalContext": "Context for Claude"
}
}"allow": Contorna o sistema de permissões"deny": Bloqueia a ferramenta e diz ao Claude porquê"ask": Pede confirmação ao utilizadorupdatedInput: Modifica os parâmetros da ferramenta antes da execução
Decisões PermissionRequest
{
"hookSpecificOutput": {
"hookEventName": "PermissionRequest",
"decision": {
"behavior": "allow",
"updatedInput": { "command": "npm run lint" }
}
}
}Aplicação de Stop/SubagentStop
{
"decision": "block",
"reason": "Tests failing. Fix them before completing."
}Hook 1: Auto-Format ao Guardar
Corre formatters após cada escrita de ficheiro:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit|MultiEdit",
"hooks": [
{
"type": "command",
"command": "npx prettier --write \"$CLAUDE_TOOL_INPUT_FILE_PATH\""
},
{
"type": "command",
"command": "npx eslint --fix \"$CLAUDE_TOOL_INPUT_FILE_PATH\""
}
]
}
]
}
}Os dois hooks correm em paralelo. Format e lint terminam antes da resposta do Claude aparecer.
Hook 2: Injeção de Contexto na Sessão
Carrega contexto no início da sessão:
{
"hooks": {
"SessionStart": [
{
"hooks": [
{
"type": "command",
"command": "echo '## Git Status' && git status --short && echo '## TODOs' && grep -r 'TODO:' src/ | head -5"
}
]
}
]
}
}Persistir Variáveis de Ambiente
Um hook SessionStart ou Setup pode definir variáveis de ambiente que ficam para a sessão toda:
#!/bin/bash
if [ -n "$CLAUDE_ENV_FILE" ]; then
echo 'export NODE_ENV=production' >> "$CLAUDE_ENV_FILE"
echo 'export API_KEY=your-key' >> "$CLAUDE_ENV_FILE"
fi
exit 0Hook 3: Bloqueio de Segurança
Para operações perigosas com PreToolUse:
#!/usr/bin/env python3
import json
import sys
import re
DANGEROUS_PATTERNS = [
r'\brm\s+.*-[a-z]*r[a-z]*f',
r'sudo\s+rm',
r'chmod\s+777',
r'git\s+push\s+--force.*main',
]
input_data = json.load(sys.stdin)
if input_data.get('tool_name') == 'Bash':
command = input_data.get('tool_input', {}).get('command', '')
for pattern in DANGEROUS_PATTERNS:
if re.search(pattern, command, re.IGNORECASE):
print("BLOCKED: Dangerous pattern", file=sys.stderr)
sys.exit(2)
sys.exit(0)Hook 4: Auto-Aprovação de Comandos Seguros
Usa PermissionRequest para saltar o prompt quando um comando é sabidamente seguro:
{
"hooks": {
"PermissionRequest": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "python .claude/hooks/auto-approve.py"
}
]
}
]
}
}#!/usr/bin/env python3
import json
import sys
SAFE_PREFIXES = ['npm test', 'npm run lint', 'git status', 'ls']
input_data = json.load(sys.stdin)
command = input_data.get('tool_input', {}).get('command', '')
for prefix in SAFE_PREFIXES:
if command.startswith(prefix):
output = {
"hookSpecificOutput": {
"hookEventName": "PermissionRequest",
"decision": {"behavior": "allow"}
}
}
print(json.dumps(output))
sys.exit(0)
sys.exit(0) # Normal flow for other commandsHook 5: Backup do Transcript
PreCompact é o momento para tirar um snapshot do transcript. Marca o hook com async: true já que um backup nunca tem de bloquear Claude:
{
"hooks": {
"PreCompact": [
{
"hooks": [
{
"type": "command",
"command": "python .claude/hooks/backup-transcript.py",
"async": true
}
]
}
]
}
}Os matchers manual e auto separam /compact da compactação automática.
Hook 6: Aplicação de Conclusão de Tarefas
Um hook Stop impede que Claude declare um trabalho concluído demasiado cedo:
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "prompt",
"prompt": "Check if all tasks are complete: $ARGUMENTS. Return {\"ok\": false, \"reason\": \"...\"} if work remains."
}
]
}
]
}
}O guia Stop Hook cobre versões baseadas em comandos deste padrão.
Hook 7: Ativação de Skills
Um Skill Activation Hook intervém no prompt antes de Claude o ver e acrescenta recomendações de skills no final. Cada palavra-chave que escreves é verificada contra um conjunto de regras, assim a skill de tuning de Postgres aparece no contexto logo que mencionas uma query lenta. Vinte e uma categorias de skills já usam este padrão dentro do SkillActivationHook do Code Kit:
{
"hooks": {
"UserPromptSubmit": [
{
"hooks": [
{
"type": "command",
"command": "node .claude/hooks/SkillActivationHook/skill-activation-prompt.mjs"
}
]
}
]
}
}Hook 8: Backups Baseados em Threshold via StatusLine
O StatusLine é o único lugar onde tens métricas de contexto em tempo real. Isso torna-o o lugar certo para lançar um backup quando atinges um threshold:
{
"statusLine": {
"type": "command",
"command": "node .claude/hooks/ContextRecoveryHook/statusline-monitor.mjs"
}
}Crítico: O campo remaining_percentage já incorpora um buffer fixo de 33K tokens de autocompact. Para obter o número real de "livre até autocompact", faz as contas tu mesmo:
const AUTOCOMPACT_BUFFER_TOKENS = 33000;
const autocompactBufferPct = (AUTOCOMPACT_BUFFER_TOKENS / windowSize) * 100;
const freeUntilCompact = Math.max(0, pctRemainTotal - autocompactBufferPct);Dois sistemas de trigger correm lado a lado dentro do script de backup. Os tokens são o trigger primário. As percentagens ficam por baixo como rede de segurança:
// Token-based triggers (primary - works across all window sizes)
const TOKEN_FIRST_BACKUP = 50000; // First backup at 50k tokens used
const TOKEN_UPDATE_INTERVAL = 10000; // Update every 10k tokens after
if (currentTotalTokens >= TOKEN_FIRST_BACKUP) {
if (lastBackupTokens < TOKEN_FIRST_BACKUP) {
runBackup(
sessionId,
`tokens_${Math.round(currentTotalTokens / 1000)}k_first`,
);
} else if (currentTotalTokens - lastBackupTokens >= TOKEN_UPDATE_INTERVAL) {
runBackup(
sessionId,
`tokens_${Math.round(currentTotalTokens / 1000)}k_update`,
);
}
}
// Percentage-based triggers (safety net, especially for 200k windows)
const THRESHOLDS = [30, 15, 5];
for (const threshold of THRESHOLDS) {
if (state.lastFree > threshold && freeUntilCompact <= threshold) {
runBackup(sessionId, `crossed_${threshold}pct`);
}
}
if (freeUntilCompact < 5 && freeUntilCompact < state.lastFree) {
runBackup(sessionId, "continuous");
}O PreCompact só dispara no momento da compactação. Os backups baseados no StatusLine tiram snapshot da sessão com antecedência, enquanto nada se partiu ainda. O trigger de tokens importa mais em janelas grandes como 1M, onde um trigger de percentagem chegaria demasiado tarde para ajudar.
Arquitetura: Estrutura de Três Ficheiros
O sistema de backup divide-se em três ficheiros com trabalhos separados:
.claude/hooks/ContextRecoveryHook/
├── backup-core.mjs # Shared backup logic (parsing, formatting, saving)
├── statusline-monitor.mjs # Threshold detection + display (calls backup-core)
└── conv-backup.mjs # PreCompact trigger (calls backup-core)| Ficheiro | Trigger | Responsabilidade |
|---|---|---|
backup-core.mjs | Chamado pelos outros | Analisa transcript, formata markdown, guarda ficheiro, atualiza estado |
statusline-monitor.mjs | StatusLine (contínuo) | Monitoriza tokens/contexto %, deteta triggers, mostra status |
conv-backup.mjs | Hook PreCompact | Trata o evento de pré-compactação |
Um ficheiro detém o trabalho real de backup. Ajusta como o markdown é renderizado, como os ficheiros são nomeados, ou como o estado é registado dentro do backup-core.mjs, e ambos os chamadores herdam a mudança automaticamente.
Nomes dos Ficheiros de Backup
Os backups usam nomes de ficheiro numerados com timestamps para o histórico ser fácil de percorrer:
.claude/backups/1-backup-26th-Jan-2026-4-30pm.md
.claude/backups/2-backup-26th-Jan-2026-5-15pm.md
.claude/backups/3-backup-26th-Jan-2026-5-45pm.mdDisplay do StatusLine
Se existe um backup para a sessão atual, o statusline mostra o seu caminho logo ali:
[!] 25.0% free (50.0K/200K)
-> .claude/backups/3-backup-26th-Jan-2026-5-45pm.mdSabes de relance qual ficheiro carregar de volta assim que a compactação correr.
Rastreio de Estado
Ambos os hooks partilham um ficheiro de estado em ~/.claude/claudefast-statusline-state.json:
{
"sessionId": "abc123",
"lastFreeUntilCompact": 25.5,
"currentBackupPath": ".claude/backups/3-backup-26th-Jan-2026-5-45pm.md"
}Fluxo recomendado: No momento em que a compactação dispara, usa /clear para um slate limpo e carrega o caminho de backup que o statusline mostrou. Caso contrário, o resumo gerado automaticamente e as tuas notas restauradas acabam por se sobrepor.
Hook 9: Setup Hooks para Instalação e Manutenção
Um hook Setup corre antes da tua sessão começar, disparado por flags especiais da CLI:
claude --init # Triggers Setup hook with matcher "init"
claude --init-only # Same as above, but exits after hook (CI-friendly)
claude --maintenance # Triggers Setup hook with matcher "maintenance"Liga-os com matchers no settings.json:
{
"hooks": {
"Setup": [
{
"matcher": "init",
"hooks": [
{
"type": "command",
"command": ".claude/hooks/setup_init.py",
"timeout": 120
}
]
},
{
"matcher": "maintenance",
"hooks": [
{
"type": "command",
"command": ".claude/hooks/setup_maintenance.py",
"timeout": 60
}
]
}
]
}
}Combina flags com prompts: Acrescenta um prompt à própria flag.
claude --init "/install"
O hook corre primeiro, determinístico. Depois o comando /install dispara, agêntico. Uma invocação, dois modos de execução: uma configuração com script seguida de um agente que raciocina sobre ela.
O Setup Hooks Guide percorre o padrão completo do início ao fim, incluindo setups com justfile e fluxos de onboarding interativos.
Localizações de Configuração
| Localização | Âmbito | Prioridade |
|---|---|---|
| Managed policy | Enterprise | Mais alta |
.claude/settings.json | Projeto (partilhado) | Alta |
.claude/settings.local.json | Projeto (pessoal) | Média |
~/.claude/settings.json | Todos os projetos | Mais baixa |
Desativar e Restringir Hooks
Desativar Todos os Hooks
Se um hook está a causar problemas ou queres uma baseline limpa, coloca disableAllHooks a true nas tuas settings:
{
"disableAllHooks": true
}Cada hook em cada âmbito fica em silêncio. Utilizador, projeto, local. Usa isto quando estás a depurar um hook problemático ou precisas de uma baseline conhecida e limpa.
Restrições de Hooks Geridos
Organizações que precisam de controlo centralizado podem definir allowManagedHooksOnly a true nas managed settings:
{
"allowManagedHooksOnly": true
}Ativa-o e os únicos hooks que o Claude Code vai correr são os declarados nas managed settings mais os hooks de SDK. Âmbito de utilizador, âmbito de projeto, âmbito de plugin: os três são ignorados. Nenhum hook local consegue contornar a postura de segurança da org.
Combina esta configuração com allowManagedPermissionRulesOnly, que restringe as regras de permissão da mesma forma. Juntas, colocam os admins a gerir toda a superfície: as próprias permissões, mais cada hook que as estende.
Sintaxe de Matchers
| Padrão | Corresponde |
|---|---|
"" ou omitido | Todas as ferramentas |
"Bash" | Só Bash (exato, case-sensitive) |
| `"Write | Edit"` |
"mcp__memory__.*" | Todas as ferramentas MCP de memory |
Crítico: Não deixes espaços em redor do |. E as strings de matcher são case-sensitive.
Matchers Específicos por Evento
SessionStart: startup, resume, clear, compactPreCompact: manual, autoSetup: init, maintenanceNotification: permission_prompt, idle_prompt, auth_success
Variáveis de Ambiente
| Variável | Descrição |
|---|---|
CLAUDE_PROJECT_DIR | Raiz do projeto (todos os hooks) |
CLAUDE_ENV_FILE | Persistir env vars (SessionStart, Setup) |
CLAUDE_CODE_REMOTE | "true" se web, vazio se CLI |
Depuração
Hook não está a disparar?
- Verifica a sintaxe do matcher (case-sensitive, sem espaços)
- Verifica a localização do ficheiro de settings
- Testa:
echo '{"session_id":"test"}' | python your-hook.py
Comando a falhar?
- Adiciona logging:
command 2>&1 | tee ~/.claude/hook-debug.log - Corre com debug:
claude --debug - Hooks a quebrar noutros sistemas operativos? Vê os padrões de hooks cross-platform
Loops infinitos com Stop?
- Verifica sempre o flag
stop_hook_activeprimeiro
Começa com Um Hook
Escolhe o ponto de fricção que mais te incomoda:
- A formatar cada ficheiro à mão? Formatter PostToolUse
- A aprovar comandos seguros o dia todo? Auto-aprovação PermissionRequest
- A sessão abre sem contexto? Injeção SessionStart
- A perder progresso para compactação? Backup PreCompact
- As tarefas são declaradas concluídas cedo demais? Aplicação Stop
Um hook. Um ponto de fricção eliminado. Depois itera. Se queres saltar a configuração, o Code Kit vem com 5 hooks já configurados: ativação de skills, auto-formatação, recuperação de contexto, automação de permissões e validação de código. Cada um deles é construído nos padrões acima.
Pare de configurar. Comece a construir.
Templates SaaS com orquestração de IA.