- 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>
1184 lines
25 KiB
Markdown
Executable File
1184 lines
25 KiB
Markdown
Executable File
# GUIA COMPLETO: Claude Code Hooks
|
|
|
|
> **Versão:** 1.0.0 | **Data:** 2026-02-04 | **Autor:** Descomplicar®
|
|
|
|
---
|
|
|
|
## Índice
|
|
|
|
1. [Arquitectura de Hooks](#1-arquitectura-de-hooks)
|
|
2. [Configuração](#2-configuração)
|
|
3. [Variáveis de Ambiente](#3-variáveis-de-ambiente)
|
|
4. [Casos de Uso Práticos](#4-casos-de-uso-práticos)
|
|
5. [Padrões e Anti-Padrões](#5-padrões-e-anti-padrões)
|
|
6. [Exemplos de Código](#6-exemplos-de-código)
|
|
7. [Limitações e Workarounds](#7-limitações-e-workarounds)
|
|
8. [Debugging e Troubleshooting](#8-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
|
|
|
|
1. **Matching:** Claude Code verifica se matcher combina com evento
|
|
2. **Parallel Execution:** Todos os hooks que combinam correm em paralelo
|
|
3. **Deduplication:** Comandos idênticos executam uma vez só
|
|
4. **Serial Blocking:** Se hook bloqueia (exit 2), acção é cancelada
|
|
5. **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
|
|
|
|
```json
|
|
{
|
|
"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:
|
|
|
|
```json
|
|
{
|
|
"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
|
|
|
|
```json
|
|
{
|
|
"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
|
|
|
|
```bash
|
|
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**:
|
|
|
|
```json
|
|
{
|
|
"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
|
|
|
|
```json
|
|
{
|
|
"source": "startup|resume|clear|compact",
|
|
"model": "claude-opus-4-5-20251101",
|
|
"agent_type": "CustomAgent"
|
|
}
|
|
```
|
|
|
|
#### UserPromptSubmit
|
|
|
|
```json
|
|
{
|
|
"prompt": "User's exact prompt text here"
|
|
}
|
|
```
|
|
|
|
#### PreToolUse (Bash Example)
|
|
|
|
```json
|
|
{
|
|
"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
|
|
|
|
```javascript
|
|
// 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
|
|
|
|
```json
|
|
{
|
|
"tool_name": "Write",
|
|
"tool_input": { },
|
|
"tool_response": { "filePath": "/path", "success": true },
|
|
"tool_use_id": "toolu_01ABC123..."
|
|
}
|
|
```
|
|
|
|
#### PostToolUseFailure
|
|
|
|
```json
|
|
{
|
|
"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
|
|
|
|
```json
|
|
{
|
|
"stop_hook_active": false
|
|
}
|
|
```
|
|
|
|
#### SubagentStop
|
|
|
|
```json
|
|
{
|
|
"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:
|
|
|
|
```bash
|
|
#!/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
|
|
|
|
```bash
|
|
#!/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:**
|
|
|
|
```json
|
|
{
|
|
"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
|
|
|
|
```bash
|
|
#!/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:**
|
|
|
|
```json
|
|
{
|
|
"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
|
|
|
|
```bash
|
|
#!/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:**
|
|
|
|
```json
|
|
{
|
|
"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
|
|
|
|
```bash
|
|
#!/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
|
|
|
|
```json
|
|
{
|
|
"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:**
|
|
|
|
```json
|
|
{
|
|
"hooks": {
|
|
"Notification": [
|
|
{
|
|
"matcher": "idle_prompt",
|
|
"hooks": [
|
|
{
|
|
"type": "command",
|
|
"command": "notify-send 'Claude Code' 'Aguarda input'"
|
|
}
|
|
]
|
|
}
|
|
]
|
|
}
|
|
}
|
|
```
|
|
|
|
**macOS:**
|
|
|
|
```json
|
|
{
|
|
"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
|
|
|
|
```json
|
|
{
|
|
"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
|
|
|
|
```bash
|
|
#!/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:
|
|
|
|
```bash
|
|
#!/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
|
|
|
|
```bash
|
|
# CORRECTO
|
|
FILE="$CLAUDE_PROJECT_DIR/.file"
|
|
jq -r ".tool_input.file_path" <<< "$INPUT"
|
|
|
|
# ERRADO
|
|
FILE=$CLAUDE_PROJECT_DIR/.file
|
|
```
|
|
|
|
#### 2. Validate JSON Input
|
|
|
|
```bash
|
|
#!/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
|
|
|
|
```bash
|
|
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
|
|
|
|
```bash
|
|
# CORRECTO
|
|
HOOK_SCRIPT="$CLAUDE_PROJECT_DIR/.claude/hooks/script.sh"
|
|
|
|
# ERRADO
|
|
HOOK_SCRIPT=".claude/hooks/script.sh"
|
|
```
|
|
|
|
#### 5. Timeout Configurado por Tipo
|
|
|
|
```json
|
|
{ "type": "command", "timeout": 30 }
|
|
{ "type": "prompt", "timeout": 30 }
|
|
{ "type": "agent", "timeout": 120 }
|
|
```
|
|
|
|
#### 6. Async para Side Effects
|
|
|
|
```json
|
|
{
|
|
"type": "command",
|
|
"async": true
|
|
}
|
|
```
|
|
|
|
### 5.2 ANTI-PADRÕES
|
|
|
|
#### ❌ Confiar em cwd
|
|
|
|
```bash
|
|
# ERRADO
|
|
cd .claude && ./hooks/script.sh
|
|
|
|
# CORRECTO
|
|
bash "$CLAUDE_PROJECT_DIR/.claude/hooks/script.sh"
|
|
```
|
|
|
|
#### ❌ Infinite Stop Hooks
|
|
|
|
```bash
|
|
# 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
|
|
|
|
```bash
|
|
# ERRADO
|
|
npx prettier --write $FILE_PATH
|
|
|
|
# CORRECTO
|
|
npx prettier --write "$FILE_PATH"
|
|
```
|
|
|
|
#### ❌ Shell profile echo statements
|
|
|
|
```bash
|
|
# ~/.zshrc - ERRADO
|
|
echo "Shell ready"
|
|
|
|
# CORRECTO
|
|
if [[ $- == *i* ]]; then
|
|
echo "Shell ready"
|
|
fi
|
|
```
|
|
|
|
#### ❌ Síncrono para long-running
|
|
|
|
```json
|
|
// 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
|
|
|
|
```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
|
|
|
|
```bash
|
|
#!/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
|
|
|
|
```bash
|
|
#!/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
|
|
|
|
```bash
|
|
#!/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
|
|
|
|
```bash
|
|
#!/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
|
|
|
|
```json
|
|
{
|
|
"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
|
|
|
|
```json
|
|
{
|
|
"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
|
|
|
|
```bash
|
|
# CORRECTO
|
|
if [ "$(jq -r '.stop_hook_active' <<< "$INPUT")" = "true" ]; then
|
|
exit 0
|
|
fi
|
|
```
|
|
|
|
#### Shell Profile Noise
|
|
|
|
```bash
|
|
# ~/.zshrc
|
|
if [[ $- == *i* ]]; then
|
|
echo "Shell ready"
|
|
fi
|
|
```
|
|
|
|
#### Hooks não Triggering
|
|
|
|
```bash
|
|
# 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
|
|
|
|
```bash
|
|
claude --debug 2>&1 | grep -i hook
|
|
```
|
|
|
|
#### 2. Verbose Mode
|
|
|
|
```
|
|
Ctrl+O # Toggle verbose mode
|
|
```
|
|
|
|
#### 3. Manual Testing
|
|
|
|
```bash
|
|
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
|
|
|
|
```bash
|
|
./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
|
|
|
|
```bash
|
|
# 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](https://code.claude.com/docs/en/hooks)
|
|
- [Automate workflows with hooks](https://code.claude.com/docs/en/hooks-guide)
|
|
- [Claude Code power user customization](https://claude.com/blog/how-to-configure-hooks)
|
|
- [GitHub - claude-code-hooks-mastery](https://github.com/disler/claude-code-hooks-mastery)
|
|
- [Complete guide to hooks - Eesel AI](https://www.eesel.ai/blog/hooks-in-claude-code)
|
|
|
|
---
|
|
|
|
**Descomplicar®** | 2026
|