- Execute all 6 migrations on Desk CRM production database - Create missing tables: cr_lsps, cr_agent_lsps, cr_lsp_usage - Create archive tables: cr_*_usage_archive (4 tables) - Create system tables: cr_migrations, cr_maintenance_log - Make all scripts executable (chmod +x) - Total cr_* tables: 38 Migration files: - 001_initial_schema.sql - 002_add_lsps.sql - 003_add_relationships.sql - 004_add_telemetry.sql - 005_add_archive_tables.sql - 006_add_maintenance_log.sql Scripts: - session-init.sh, session-end.sh - inject-context.sh, inject-agent-context.sh - record-usage.sh, db-backup.sh, sync-to-mysql.sh Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
25 KiB
Executable File
GUIA COMPLETO: Claude Code Hooks
Versão: 1.0.0 | Data: 2026-02-04 | Autor: Descomplicar®
Índice
- Arquitectura de Hooks
- Configuração
- Variáveis de Ambiente
- Casos de Uso Práticos
- Padrões e Anti-Padrões
- Exemplos de Código
- Limitações e Workarounds
- Debugging e Troubleshooting
1. ARQUITECTURA DE HOOKS
1.1 O que são Hooks?
Hooks são comandos shell ou prompts LLM que executam automaticamente em pontos específicos do ciclo de vida do Claude Code. Funcionam como checkpoints programáveis que permitem:
- Injectar lógica custom
- Enforçar padrões de código
- Integrar ferramentas externas
- Validar acções antes/depois da execução
Diferença entre Hooks e Skills:
| Aspecto | Hooks | Skills |
|---|---|---|
| Execução | Determinística (sempre que evento dispara) | Por decisão do agente |
| Trigger | Automático por evento | Quando Claude escolhe usar |
| Controlo | Total sobre flow | Sugestivo |
1.2 Ciclo de Vida Completo
SessionStart
↓
UserPromptSubmit → [Hook: validar/enriquecer prompt]
↓
PreToolUse → [Hook: bloquear/modificar tool]
↓
PermissionRequest → [Hook: auto-aprovar/negar]
↓
[Tool Execution]
↓
PostToolUse → [Hook: formatar/validar resultado] ou
PostToolUseFailure → [Hook: log erro/recuperação]
↓
SubagentStart → [Hook: enriquecer contexto subagent]
↓
SubagentStop → [Hook: validar resultado subagent]
↓
Stop → [Hook: decidir se continua]
↓
PreCompact → [Hook: preparar compactação]
↓
SessionEnd → [Hook: cleanup/logging]
1.3 Ordem de Execução
- Matching: Claude Code verifica se matcher combina com evento
- Parallel Execution: Todos os hooks que combinam correm em paralelo
- Deduplication: Comandos idênticos executam uma vez só
- Serial Blocking: Se hook bloqueia (exit 2), acção é cancelada
- Output Processing: Claude Code processa JSON output
Importante: Hooks rodam sequencialmente em relação a tool calls mas em paralelo entre si.
1.4 Eventos Disponíveis (12 total)
| Evento | Quando | Bloqueia? | Matcher Support |
|---|---|---|---|
| SessionStart | Início/resumo sessão | Não | startup, resume, clear, compact |
| UserPromptSubmit | Antes Claude processar prompt | Sim | Nenhum (sempre fire) |
| PreToolUse | Antes tool executar | Sim | Tool name (Bash, Edit, Write, etc) |
| PermissionRequest | Quando permission dialog mostra | Sim | Tool name |
| PostToolUse | Após tool sucesso | Não | Tool name |
| PostToolUseFailure | Após tool falha | Não | Tool name |
| Notification | Quando notificação dispara | Não | permission_prompt, idle_prompt, etc |
| SubagentStart | Quando subagent spawns | Não | Agent name (Explore, Plan, etc) |
| SubagentStop | Quando subagent termina | Sim | Agent name |
| Stop | Quando Claude termina resposta | Sim | Nenhum (sempre fire) |
| PreCompact | Antes compactação contexto | Não | manual, auto |
| SessionEnd | Quando sessão termina | Não | clear, logout, prompt_input_exit, other |
1.5 Tipos de Hooks
Hook
├── Command Hook (type: "command")
│ ├── Sync (default)
│ └── Async (async: true) ← Não bloqueia Claude
├── Prompt Hook (type: "prompt")
│ └── Single-turn LLM evaluation
└── Agent Hook (type: "agent")
└── Multi-turn LLM com tool access
2. CONFIGURAÇÃO
2.1 Ficheiros de Configuração
Hooks são definidos em JSON settings com hierarquia de scopes:
~/.claude/settings.json (User scope - Global)
↑ (precedência)
.claude/settings.json (Project scope - Shared via git)
↑
.claude/settings.local.json (Project scope - .gitignored)
↑
Managed policy settings (Organization - Highest precedence)
↑
Plugin hooks/hooks.json (When plugin enabled)
↑
Skill/Agent frontmatter (While active)
Regra Precedência: Higher scope sobrescreve lower scope. Não é cumulativo.
2.2 Estrutura JSON Completa
{
"hooks": {
"EventName": [
{
"matcher": "regex_pattern_or_*",
"hooks": [
{
"type": "command|prompt|agent",
"command": "shell_command_or_script_path",
"prompt": "LLM prompt text",
"model": "sonnet|haiku|opus",
"timeout": 600,
"async": false,
"statusMessage": "Custom spinner text"
}
]
}
]
}
}
Campos por tipo:
| Campo | Command | Prompt | Agent |
|---|---|---|---|
type |
✓ | ✓ | ✓ |
command |
✓ | - | - |
prompt |
- | ✓ | ✓ |
model |
- | ✓ | ✓ |
timeout |
✓ | ✓ | ✓ |
async |
✓ | - | - |
statusMessage |
✓ | ✓ | ✓ |
2.3 Matcher Patterns (Regex)
Matchers são regex strings que filtram eventos:
{
"matcher": "Edit|Write",
"matcher": "Bash",
"matcher": "mcp__memory__.*",
"matcher": "mcp__.*__write.*",
"matcher": "*",
"matcher": ""
}
Matcher por evento:
| Evento | Matcher contra |
|---|---|
| PreToolUse/PostToolUse/PermissionRequest | tool_name (Bash, Write, Edit, Read, Glob, Grep, Task, WebFetch, WebSearch) |
| SessionStart | source (startup, resume, clear, compact) |
| SessionEnd | reason (clear, logout, prompt_input_exit, other) |
| Notification | notification_type (permission_prompt, idle_prompt) |
| SubagentStart/SubagentStop | agent_type (Bash, Explore, Plan, custom) |
| PreCompact | trigger (manual, auto) |
| UserPromptSubmit/Stop | SEM matcher (sempre fire) |
2.4 Path References com Variáveis
{
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/my-script.sh"
}
Variáveis disponíveis:
| Variável | Descrição |
|---|---|
$CLAUDE_PROJECT_DIR |
Raiz do projecto |
${CLAUDE_PLUGIN_ROOT} |
Raiz do plugin |
| Qualquer env var | Variáveis do sistema |
2.5 Gestão via Menu Interactivo
claude
/hooks
O menu permite:
- Ver todos hooks por evento
- Adicionar novo hook
- Deletar hook existente
- Desabilitar temporariamente todos hooks
- Validar JSON syntax
Nota: Edições directas a ficheiros JSON só tomam efeito após reload via /hooks ou restart da sessão.
3. VARIÁVEIS DE AMBIENTE
3.1 Input JSON (via stdin)
Todos os hooks recebem JSON estruturado via stdin:
{
"session_id": "abc123-def456",
"transcript_path": "/path/to/transcript.jsonl",
"cwd": "/current/working/dir",
"permission_mode": "default|plan|acceptEdits|dontAsk|bypassPermissions",
"hook_event_name": "PreToolUse"
}
3.2 Input Schema por Evento
SessionStart
{
"source": "startup|resume|clear|compact",
"model": "claude-opus-4-5-20251101",
"agent_type": "CustomAgent"
}
UserPromptSubmit
{
"prompt": "User's exact prompt text here"
}
PreToolUse (Bash Example)
{
"tool_name": "Bash",
"tool_use_id": "toolu_01ABC123...",
"tool_input": {
"command": "npm test",
"description": "Run test suite",
"timeout": 120000,
"run_in_background": false
}
}
Tool-specific Input Schemas
// Write tool
tool_input: { "file_path": "/path/to/file", "content": "file content" }
// Edit tool
tool_input: { "file_path": "/path/file", "old_string": "...", "new_string": "...", "replace_all": false }
// Read tool
tool_input: { "file_path": "/path/file", "offset": 10, "limit": 50 }
// Glob tool
tool_input: { "pattern": "**/*.ts", "path": "/optional/dir" }
// Grep tool
tool_input: { "pattern": "TODO.*fix", "path": "/dir", "glob": "*.ts", "output_mode": "content", "-i": true }
// WebFetch tool
tool_input: { "url": "https://api.example.com", "prompt": "Extract endpoints" }
// WebSearch tool
tool_input: { "query": "react hooks", "allowed_domains": [...], "blocked_domains": [...] }
// Task tool (Subagent)
tool_input: { "prompt": "...", "description": "...", "subagent_type": "Explore", "model": "sonnet" }
PostToolUse
{
"tool_name": "Write",
"tool_input": { },
"tool_response": { "filePath": "/path", "success": true },
"tool_use_id": "toolu_01ABC123..."
}
PostToolUseFailure
{
"tool_name": "Bash",
"tool_input": { "command": "npm test" },
"tool_use_id": "toolu_01ABC123...",
"error": "Command exited with non-zero status code 1",
"is_interrupt": false
}
Stop
{
"stop_hook_active": false
}
SubagentStop
{
"stop_hook_active": false,
"agent_id": "agent-abc123",
"agent_type": "Explore",
"agent_transcript_path": "~/.claude/.../subagents/agent-abc123.jsonl"
}
3.3 Environment Variables
Hook scripts herdam todas env vars do sistema + especiais:
| Variável | Descrição | Disponível em |
|---|---|---|
CLAUDE_PROJECT_DIR |
Raiz do projecto | Todos |
CLAUDE_CODE_REMOTE |
"true" se cloud | Todos |
CLAUDE_ENV_FILE |
Path para persistir vars | SessionStart apenas |
3.4 Persistir Environment Variables (SessionStart)
Apenas SessionStart hooks podem settar env vars permanentemente:
#!/bin/bash
# ~/.claude/hooks/setup-env.sh
if [ -n "$CLAUDE_ENV_FILE" ]; then
echo 'export NODE_ENV=production' >> "$CLAUDE_ENV_FILE"
echo 'export DEBUG=1' >> "$CLAUDE_ENV_FILE"
echo 'export PATH="$PATH:./node_modules/.bin"' >> "$CLAUDE_ENV_FILE"
fi
exit 0
4. CASOS DE USO PRÁTICOS
4.1 Validação e Bloqueio Pré-Execução
Caso: Bloquear comandos perigosos
#!/bin/bash
# .claude/hooks/block-dangerous.sh
INPUT=$(cat)
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty')
if echo "$COMMAND" | grep -qE 'rm -rf|DROP TABLE|DELETE FROM'; then
jq -n '{
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "deny",
"permissionDecisionReason": "Comando destrutivo bloqueado."
}
}'
exit 0
fi
exit 0
Config:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/block-dangerous.sh"
}
]
}
]
}
}
4.2 Auto-Formatação Pós-Edição
Caso: Rodar Prettier/Ruff após file write
#!/bin/bash
# .claude/hooks/format-after-write.sh
INPUT=$(cat)
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')
case "$FILE_PATH" in
*.ts|*.tsx|*.js|*.jsx)
npx prettier --write "$FILE_PATH" 2>/dev/null
;;
*.py)
ruff format "$FILE_PATH" 2>/dev/null
;;
*.go)
gofmt -w "$FILE_PATH" 2>/dev/null
;;
esac
exit 0
Config:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/format-after-write.sh",
"timeout": 30
}
]
}
]
}
}
4.3 Logging e Auditoria
Caso: Log de todos bash commands
#!/bin/bash
# .claude/hooks/audit-bash.sh
INPUT=$(cat)
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty')
TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
echo "$TIMESTAMP | $COMMAND" >> ~/.claude/bash-audit.log
exit 0
Config:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/audit-bash.sh",
"async": true
}
]
}
]
}
}
4.4 Protecção de Ficheiros Sensíveis
Caso: Prevenir edições a .env, package-lock.json, .git
#!/bin/bash
# .claude/hooks/protect-files.sh
INPUT=$(cat)
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')
PROTECTED_PATTERNS=(".env" "package-lock.json" ".git/" "secrets/" "credentials")
for pattern in "${PROTECTED_PATTERNS[@]}"; do
if [[ "$FILE_PATH" == *"$pattern"* ]]; then
jq -n '{
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "deny",
"permissionDecisionReason": "Ficheiro protegido: '"$pattern"'"
}
}'
exit 0
fi
done
exit 0
4.5 Re-inject Context após Compactação
Caso: Re-injectar regras do projecto após compact
{
"hooks": {
"SessionStart": [
{
"matcher": "compact",
"hooks": [
{
"type": "command",
"command": "echo 'CONTEXT RE-INJECTED:\n\n1. Use Bun, not npm\n2. Run bun test before commits\n3. Follow functional style'"
}
]
}
]
}
}
4.6 Notificações Desktop
Linux:
{
"hooks": {
"Notification": [
{
"matcher": "idle_prompt",
"hooks": [
{
"type": "command",
"command": "notify-send 'Claude Code' 'Aguarda input'"
}
]
}
]
}
}
macOS:
{
"hooks": {
"Notification": [
{
"matcher": "idle_prompt",
"hooks": [
{
"type": "command",
"command": "osascript -e 'display notification \"Claude waiting\" with title \"Claude Code\"'"
}
]
}
]
}
}
4.7 Validação de Testes antes de Stop
Caso: Não deixar Claude parar se testes falharem
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "agent",
"prompt": "Run the test suite. Respond with {\"ok\": true} if all tests pass, or {\"ok\": false, \"reason\": \"Test failed\"} if any fail. $ARGUMENTS",
"timeout": 120
}
]
}
]
}
}
4.8 Enriquecer Contexto no UserPromptSubmit
Caso: Auto-adicionar info de git/time
#!/bin/bash
# .claude/hooks/enrich-context.sh
echo ""
echo "=== CONTEXT ==="
echo "Last 3 commits:"
git log --oneline -3 2>/dev/null || echo "No git repo"
echo ""
echo "Modified files:"
git status --short 2>/dev/null | head -5
exit 0
4.9 Modificação de Input (v2.0.10+)
Novidade: PreToolUse pode modificar tool inputs antes da execução:
#!/bin/bash
# .claude/hooks/force-dry-run.sh
INPUT=$(cat)
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty')
# Forçar --dry-run em comandos npm
if [[ "$COMMAND" == npm* ]] && [[ "$COMMAND" != *"--dry-run"* ]]; then
MODIFIED="$COMMAND --dry-run"
jq -n --arg cmd "$MODIFIED" '{
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"modifiedInput": {
"command": $cmd
}
}
}'
exit 0
fi
exit 0
5. PADRÕES E ANTI-PADRÕES
5.1 BOAS PRÁTICAS
1. Always Quote Variables
# CORRECTO
FILE="$CLAUDE_PROJECT_DIR/.file"
jq -r ".tool_input.file_path" <<< "$INPUT"
# ERRADO
FILE=$CLAUDE_PROJECT_DIR/.file
2. Validate JSON Input
#!/bin/bash
INPUT=$(cat)
if ! echo "$INPUT" | jq empty 2>/dev/null; then
echo "Invalid JSON input" >&2
exit 1
fi
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty')
3. Block Path Traversal
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')
if [[ "$FILE_PATH" == *".."* ]]; then
echo "Path traversal detected" >&2
exit 2
fi
4. Use Absolute Paths
# CORRECTO
HOOK_SCRIPT="$CLAUDE_PROJECT_DIR/.claude/hooks/script.sh"
# ERRADO
HOOK_SCRIPT=".claude/hooks/script.sh"
5. Timeout Configurado por Tipo
{ "type": "command", "timeout": 30 }
{ "type": "prompt", "timeout": 30 }
{ "type": "agent", "timeout": 120 }
6. Async para Side Effects
{
"type": "command",
"async": true
}
5.2 ANTI-PADRÕES
❌ Confiar em cwd
# ERRADO
cd .claude && ./hooks/script.sh
# CORRECTO
bash "$CLAUDE_PROJECT_DIR/.claude/hooks/script.sh"
❌ Infinite Stop Hooks
# ERRADO - loop infinito
if [ some condition ]; then
echo '{"decision": "block"}'
fi
# CORRECTO
INPUT=$(cat)
if [ "$(echo "$INPUT" | jq -r '.stop_hook_active')" = "true" ]; then
exit 0
fi
❌ Não citar paths com spaces
# ERRADO
npx prettier --write $FILE_PATH
# CORRECTO
npx prettier --write "$FILE_PATH"
❌ Shell profile echo statements
# ~/.zshrc - ERRADO
echo "Shell ready"
# CORRECTO
if [[ $- == *i* ]]; then
echo "Shell ready"
fi
❌ Síncrono para long-running
// ERRADO
{ "command": "npm run test-suite", "async": false }
// CORRECTO
{ "command": "npm run test-suite", "async": true }
5.3 Performance Considerations
| Padrão | Impacto | Recomendação |
|---|---|---|
| Multiple hooks mesmo evento | O(n) paralelo | OK até 5 hooks |
| Timeout muito alto | Bloqueia Claude | Use defaults |
| Shell scripts lentos | Latência | Considerar async |
| JQ parsing complex | CPU intensive | Python/Node |
| File I/O em hook | Disk latency | Minimizar |
Benchmarks típicos:
| Operação | Tempo |
|---|---|
| Simple bash | 10-50ms |
| JQ parsing | 5-20ms |
| Prettier format | 100-500ms |
| Test suite | 5-30s |
6. EXEMPLOS DE CÓDIGO
6.1 Validador Inteligente de Bash
#!/bin/bash
# .claude/hooks/validate-bash.sh
set -euo pipefail
INPUT=$(cat)
if ! COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty' 2>/dev/null); then
echo "Failed to parse hook input" >&2
exit 1
fi
declare -a DENY_PATTERNS=(
'rm -rf'
'DROP TABLE'
'DELETE FROM'
'> /etc/'
'sudo'
'chmod 777'
)
for pattern in "${DENY_PATTERNS[@]}"; do
if echo "$COMMAND" | grep -qiF "$pattern"; then
jq -n '{
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "deny",
"permissionDecisionReason": "Padrão perigoso: '"$pattern"'"
}
}'
exit 0
fi
done
exit 0
6.2 Code Quality Enforcement
#!/bin/bash
# .claude/hooks/enforce-quality.sh
INPUT=$(cat)
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')
[ -z "$FILE_PATH" ] && exit 0
case "$FILE_PATH" in
*.ts|*.tsx|*.js|*.jsx)
npx eslint --fix "$FILE_PATH" 2>/dev/null || true
npx prettier --write "$FILE_PATH" 2>/dev/null || true
;;
*.py)
ruff format "$FILE_PATH" 2>/dev/null || true
mypy "$FILE_PATH" 2>/dev/null || true
;;
*.go)
gofmt -w "$FILE_PATH" 2>/dev/null || true
;;
*.rs)
rustfmt "$FILE_PATH" 2>/dev/null || true
;;
esac
exit 0
6.3 Auto-Approval com Lógica Custom
#!/bin/bash
# .claude/hooks/smart-permissions.sh
INPUT=$(cat)
TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name // empty')
# Read-only tools: auto-approve
case "$TOOL_NAME" in
Read|Glob|Grep|WebFetch|WebSearch)
jq -n '{
"hookSpecificOutput": {
"hookEventName": "PermissionRequest",
"decision": { "behavior": "allow" }
}
}'
exit 0
;;
esac
exit 0
6.4 Context Injection na Sessão
#!/bin/bash
# .claude/hooks/inject-session-context.sh
CONTEXT=""
if [ -d .git ]; then
CONTEXT+="=== Git Status ===$(printf '\n')"
CONTEXT+="$(git status --short | head -5)$(printf '\n\n')"
CONTEXT+="=== Recent Commits ===$(printf '\n')"
CONTEXT+="$(git log --oneline -3)$(printf '\n\n')"
fi
if [ -f package.json ]; then
CONTEXT+="=== Test Suite ===$(printf '\n')"
CONTEXT+="Ready to run: npm test$(printf '\n\n')"
fi
jq -n --arg ctx "$CONTEXT" '{
"hookSpecificOutput": {
"hookEventName": "SessionStart",
"additionalContext": $ctx
}
}'
exit 0
6.5 Async Test Runner
#!/bin/bash
# .claude/hooks/run-tests-async.sh
INPUT=$(cat)
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')
[[ ! "$FILE_PATH" =~ \.(ts|js|py|rs)$ ]] && exit 0
TEST_OUTPUT=$(npm test 2>&1 || true)
EXIT_CODE=$?
if [ $EXIT_CODE -eq 0 ]; then
jq -n '{
"systemMessage": "✓ Tests passed"
}'
else
jq -n --arg output "$TEST_OUTPUT" '{
"systemMessage": "✗ Tests failed",
"additionalContext": "Test output:\n" + $output
}'
fi
exit 0
6.6 Prompt-Based Hook: Stop Decision
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "prompt",
"prompt": "Evaluate if Claude should stop. Check:\n1. All tasks complete?\n2. Any errors need fixing?\n3. Follow-up needed?\n\nRespond: {\"ok\": true} or {\"ok\": false, \"reason\": \"...\"}",
"timeout": 30,
"model": "sonnet"
}
]
}
]
}
}
6.7 Agent-Based Hook: Test Validation
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "agent",
"prompt": "Verify all tests pass. Use Bash to run test suite. Respond {\"ok\": true} if pass or {\"ok\": false, \"reason\": \"...\"} if fail.",
"timeout": 300,
"model": "sonnet"
}
]
}
]
}
}
7. LIMITAÇÕES E WORKAROUNDS
7.1 Limitações Técnicas
| Limitação | Detalhe | Workaround |
|---|---|---|
| Sem tool calls directos | Hooks não chamam tools | Agent hooks |
| PostToolUse não bloqueia | Tool já executou | Use PreToolUse |
| Timeout 10min default | Long-running bloqueia | async: true |
| Sem slash commands | Não pode /compact |
Script manipulation |
| Single-turn prompts | One-shot apenas | Agent hooks |
| Matcher case-sensitive | "Bash" ≠ "bash" | Exact case |
| PermissionRequest em -p | Não dispara | Use PreToolUse |
7.2 Workarounds Comuns
Infinite Stop Loop
# CORRECTO
if [ "$(jq -r '.stop_hook_active' <<< "$INPUT")" = "true" ]; then
exit 0
fi
Shell Profile Noise
# ~/.zshrc
if [[ $- == *i* ]]; then
echo "Shell ready"
fi
Hooks não Triggering
# Debug checklist:
claude --debug
# 1. Matcher case-sensitive?
# 2. JSON syntax valid?
# 3. Ficheiro executable?
# 4. Event name correcto?
# 5. File path existe?
7.3 Feature Gaps
| Feature | Status | Alternative |
|---|---|---|
| Hook para Custom Subagents | ❌ | Skill frontmatter |
| Hook Output Transforms | Parcial | JSON output fields |
| Conditional Execution | ❌ | Prompt/Agent hooks |
| Hook Chaining | ❌ | Multiple hooks |
| Rollback Actions | ❌ | Logging + manual |
8. DEBUGGING E TROUBLESHOOTING
8.1 Técnicas de Debug
1. Debug Mode
claude --debug 2>&1 | grep -i hook
2. Verbose Mode
Ctrl+O # Toggle verbose mode
3. Manual Testing
echo '{
"session_id": "test",
"hook_event_name": "PreToolUse",
"tool_name": "Bash",
"tool_input": { "command": "rm -rf /tmp" }
}' | /path/to/hook.sh
echo "Exit code: $?"
4. JSON Validation
./hook.sh < input.json | jq empty
echo "Valid: $?"
8.2 Common Errors
| Erro | Causa | Solução |
|---|---|---|
command not found |
Path incorrecto | $CLAUDE_PROJECT_DIR |
JSON validation failed |
Invalid output | Test com jq empty |
jq: command not found |
jq não instalado | apt install jq |
Permission denied |
Não executable | chmod +x |
Hook timed out |
Script lento | Aumentar timeout |
Exit code 127 |
Script not found | Verificar path |
Matcher mismatch |
Case errado | Use correct case |
8.3 Troubleshooting Checklist
1. [ ] Hook aparece em /hooks menu?
2. [ ] Hook dispara quando esperado?
3. [ ] Script executa correctamente?
4. [ ] JSON output processado?
5. [ ] Performance aceitável?
6. [ ] Infinite loops ou side effects?
8.4 Debug Session Completa
# 1. Run with debug
$ claude --debug 2>&1 | tee debug.log
# 2. Look for errors
$ grep -i "hook.*error" debug.log
# 3. Test manually
$ cat > test_input.json << 'EOF'
{
"session_id": "test",
"hook_event_name": "PreToolUse",
"tool_name": "Bash",
"tool_input": {"command": "npm test"}
}
EOF
$ cat test_input.json | .claude/hooks/my-hook.sh
$ echo "Exit code: $?"
# 4. Validate JSON
$ cat test_input.json | .claude/hooks/my-hook.sh | jq empty
# 5. Check matcher
$ grep -A2 "matcher" .claude/settings.json
# 6. Reload hooks
/hooks # no Claude Code
REFERÊNCIAS
- Hooks reference - Claude Code Docs
- Automate workflows with hooks
- Claude Code power user customization
- GitHub - claude-code-hooks-mastery
- Complete guide to hooks - Eesel AI
Descomplicar® | 2026