Per una guida rapida con esempi, vedi Inizia con gli hooks di Claude Code.

Configurazione

Gli hooks di Claude Code sono configurati nei tuoi file di impostazioni:

  • ~/.claude/settings.json - Impostazioni utente
  • .claude/settings.json - Impostazioni progetto
  • .claude/settings.local.json - Impostazioni progetto locali (non committate)
  • Impostazioni policy gestite dall’azienda

Struttura

Gli hooks sono organizzati per matcher, dove ogni matcher può avere più hooks:

{
  "hooks": {
    "EventName": [
      {
        "matcher": "ToolPattern",
        "hooks": [
          {
            "type": "command",
            "command": "your-command-here"
          }
        ]
      }
    ]
  }
}
  • matcher: Pattern per abbinare i nomi degli strumenti, case-sensitive (applicabile solo per PreToolUse e PostToolUse)
    • Le stringhe semplici corrispondono esattamente: Write corrisponde solo allo strumento Write
    • Supporta regex: Edit|Write o Notebook.*
    • Usa * per abbinare tutti gli strumenti. Puoi anche usare una stringa vuota ("") o lasciare matcher vuoto.
  • hooks: Array di comandi da eseguire quando il pattern corrisponde
    • type: Attualmente è supportato solo "command"
    • command: Il comando bash da eseguire (può usare la variabile d’ambiente $CLAUDE_PROJECT_DIR)
    • timeout: (Opzionale) Quanto tempo dovrebbe durare un comando, in secondi, prima di cancellare quel comando specifico.

Per eventi come UserPromptSubmit, Notification, Stop, e SubagentStop che non usano matcher, puoi omettere il campo matcher:

{
  "hooks": {
    "UserPromptSubmit": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "/path/to/prompt-validator.py"
          }
        ]
      }
    ]
  }
}

Script Hook Specifici del Progetto

Puoi usare la variabile d’ambiente CLAUDE_PROJECT_DIR (disponibile solo quando Claude Code genera il comando hook) per riferire script memorizzati nel tuo progetto, assicurandoti che funzionino indipendentemente dalla directory corrente di Claude:

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "command",
            "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/check-style.sh"
          }
        ]
      }
    ]
  }
}

Eventi Hook

PreToolUse

Viene eseguito dopo che Claude crea i parametri dello strumento e prima di processare la chiamata dello strumento.

Matcher comuni:

  • Task - Compiti subagent (vedi documentazione subagents)
  • Bash - Comandi shell
  • Glob - Corrispondenza pattern file
  • Grep - Ricerca contenuto
  • Read - Lettura file
  • Edit, MultiEdit - Modifica file
  • Write - Scrittura file
  • WebFetch, WebSearch - Operazioni web

PostToolUse

Viene eseguito immediatamente dopo che uno strumento completa con successo.

Riconosce gli stessi valori matcher di PreToolUse.

Notification

Viene eseguito quando Claude Code invia notifiche. Le notifiche vengono inviate quando:

  1. Claude ha bisogno del tuo permesso per usare uno strumento. Esempio: “Claude ha bisogno del tuo permesso per usare Bash”
  2. L’input del prompt è rimasto inattivo per almeno 60 secondi. “Claude sta aspettando il tuo input”

UserPromptSubmit

Viene eseguito quando l’utente invia un prompt, prima che Claude lo processi. Questo ti permette di aggiungere contesto aggiuntivo basato sul prompt/conversazione, validare prompt, o bloccare certi tipi di prompt.

Stop

Viene eseguito quando l’agente principale Claude Code ha finito di rispondere. Non viene eseguito se l’arresto è avvenuto a causa di un’interruzione dell’utente.

SubagentStop

Viene eseguito quando un subagent Claude Code (chiamata strumento Task) ha finito di rispondere.

SessionEnd

Viene eseguito quando una sessione Claude Code termina. Utile per compiti di pulizia, registrazione statistiche sessione, o salvataggio stato sessione.

Il campo reason nell’input hook sarà uno di:

  • clear - Sessione cancellata con comando /clear
  • logout - Utente disconnesso
  • prompt_input_exit - Utente uscito mentre l’input prompt era visibile
  • other - Altri motivi di uscita

PreCompact

Viene eseguito prima che Claude Code stia per eseguire un’operazione compact.

Matcher:

  • manual - Invocato da /compact
  • auto - Invocato da auto-compact (a causa di finestra contesto piena)

SessionStart

Viene eseguito quando Claude Code inizia una nuova sessione o riprende una sessione esistente (che attualmente inizia una nuova sessione sotto il cofano). Utile per caricare contesto di sviluppo come problemi esistenti o cambiamenti recenti alla tua codebase.

Matcher:

  • startup - Invocato dall’avvio
  • resume - Invocato da --resume, --continue, o /resume
  • clear - Invocato da /clear

Input Hook

Gli hooks ricevono dati JSON via stdin contenenti informazioni sulla sessione e dati specifici dell’evento:

{
  // Campi comuni
  session_id: string
  transcript_path: string  // Percorso al JSON conversazione
  cwd: string              // La directory di lavoro corrente quando l'hook viene invocato

  // Campi specifici dell'evento
  hook_event_name: string
  ...
}

Input PreToolUse

Lo schema esatto per tool_input dipende dallo strumento.

{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "cwd": "/Users/...",
  "hook_event_name": "PreToolUse",
  "tool_name": "Write",
  "tool_input": {
    "file_path": "/path/to/file.txt",
    "content": "file content"
  }
}

Input PostToolUse

Lo schema esatto per tool_input e tool_response dipende dallo strumento.

{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "cwd": "/Users/...",
  "hook_event_name": "PostToolUse",
  "tool_name": "Write",
  "tool_input": {
    "file_path": "/path/to/file.txt",
    "content": "file content"
  },
  "tool_response": {
    "filePath": "/path/to/file.txt",
    "success": true
  }
}

Input Notification

{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "cwd": "/Users/...",
  "hook_event_name": "Notification",
  "message": "Task completed successfully"
}

Input UserPromptSubmit

{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "cwd": "/Users/...",
  "hook_event_name": "UserPromptSubmit",
  "prompt": "Write a function to calculate the factorial of a number"
}

Input Stop e SubagentStop

stop_hook_active è true quando Claude Code sta già continuando come risultato di un hook stop. Controlla questo valore o processa il transcript per prevenire che Claude Code funzioni indefinitamente.

{
  "session_id": "abc123",
  "transcript_path": "~/.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "hook_event_name": "Stop",
  "stop_hook_active": true
}

Input PreCompact

Per manual, custom_instructions viene da quello che l’utente passa in /compact. Per auto, custom_instructions è vuoto.

{
  "session_id": "abc123",
  "transcript_path": "~/.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "hook_event_name": "PreCompact",
  "trigger": "manual",
  "custom_instructions": ""
}

Input SessionStart

{
  "session_id": "abc123",
  "transcript_path": "~/.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "hook_event_name": "SessionStart",
  "source": "startup"
}

Input SessionEnd

{
  "session_id": "abc123",
  "transcript_path": "~/.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "cwd": "/Users/...",
  "hook_event_name": "SessionEnd",
  "reason": "exit"
}

Output Hook

Ci sono due modi per gli hooks di restituire output a Claude Code. L’output comunica se bloccare e qualsiasi feedback che dovrebbe essere mostrato a Claude e all’utente.

Semplice: Codice di Uscita

Gli hooks comunicano lo stato attraverso codici di uscita, stdout, e stderr:

  • Codice di uscita 0: Successo. stdout è mostrato all’utente in modalità transcript (CTRL-R), eccetto per UserPromptSubmit e SessionStart, dove stdout è aggiunto al contesto.
  • Codice di uscita 2: Errore bloccante. stderr è restituito a Claude per processare automaticamente. Vedi comportamento per-hook-event sotto.
  • Altri codici di uscita: Errore non bloccante. stderr è mostrato all’utente e l’esecuzione continua.

Promemoria: Claude Code non vede stdout se il codice di uscita è 0, eccetto per l’hook UserPromptSubmit dove stdout è iniettato come contesto.

Comportamento Codice di Uscita 2

Evento HookComportamento
PreToolUseBlocca la chiamata strumento, mostra stderr a Claude
PostToolUseMostra stderr a Claude (strumento già eseguito)
NotificationN/A, mostra stderr solo all’utente
UserPromptSubmitBlocca processamento prompt, cancella prompt, mostra stderr solo all’utente
StopBlocca arresto, mostra stderr a Claude
SubagentStopBlocca arresto, mostra stderr al subagent Claude
PreCompactN/A, mostra stderr solo all’utente
SessionStartN/A, mostra stderr solo all’utente
SessionEndN/A, mostra stderr solo all’utente

Avanzato: Output JSON

Gli hooks possono restituire JSON strutturato in stdout per controllo più sofisticato:

Campi JSON Comuni

Tutti i tipi di hook possono includere questi campi opzionali:

{
  "continue": true, // Se Claude dovrebbe continuare dopo l'esecuzione hook (default: true)
  "stopReason": "string", // Messaggio mostrato quando continue è false

  "suppressOutput": true, // Nascondi stdout dalla modalità transcript (default: false)
  "systemMessage": "string" // Messaggio di avviso opzionale mostrato all'utente
}

Se continue è false, Claude smette di processare dopo che gli hooks vengono eseguiti.

  • Per PreToolUse, questo è diverso da "permissionDecision": "deny", che blocca solo una chiamata strumento specifica e fornisce feedback automatico a Claude.
  • Per PostToolUse, questo è diverso da "decision": "block", che fornisce feedback automatizzato a Claude.
  • Per UserPromptSubmit, questo previene che il prompt venga processato.
  • Per Stop e SubagentStop, questo ha precedenza su qualsiasi output "decision": "block".
  • In tutti i casi, "continue" = false ha precedenza su qualsiasi output "decision": "block".

stopReason accompagna continue con un motivo mostrato all’utente, non mostrato a Claude.

Controllo Decisione PreToolUse

Gli hooks PreToolUse possono controllare se una chiamata strumento procede.

  • "allow" bypassa il sistema di permessi. permissionDecisionReason è mostrato all’utente ma non a Claude.
  • "deny" previene l’esecuzione della chiamata strumento. permissionDecisionReason è mostrato a Claude.
  • "ask" chiede all’utente di confermare la chiamata strumento nell’UI. permissionDecisionReason è mostrato all’utente ma non a Claude.
{
  "hookSpecificOutput": {
    "hookEventName": "PreToolUse",
    "permissionDecision": "allow" | "deny" | "ask",
    "permissionDecisionReason": "My reason here"
  }
}

I campi decision e reason sono deprecati per gli hooks PreToolUse. Usa hookSpecificOutput.permissionDecision e hookSpecificOutput.permissionDecisionReason invece. I campi deprecati "approve" e "block" mappano a "allow" e "deny" rispettivamente.

Controllo Decisione PostToolUse

Gli hooks PostToolUse possono fornire feedback a Claude dopo l’esecuzione dello strumento.

  • "block" richiede automaticamente a Claude con reason.
  • undefined non fa nulla. reason è ignorato.
  • "hookSpecificOutput.additionalContext" aggiunge contesto per Claude da considerare.
{
  "decision": "block" | undefined,
  "reason": "Explanation for decision",
  "hookSpecificOutput": {
    "hookEventName": "PostToolUse",
    "additionalContext": "Additional information for Claude"
  }
}

Controllo Decisione UserPromptSubmit

Gli hooks UserPromptSubmit possono controllare se un prompt utente viene processato.

  • "block" previene che il prompt venga processato. Il prompt inviato è cancellato dal contesto. "reason" è mostrato all’utente ma non aggiunto al contesto.
  • undefined permette al prompt di procedere normalmente. "reason" è ignorato.
  • "hookSpecificOutput.additionalContext" aggiunge la stringa al contesto se non bloccato.
{
  "decision": "block" | undefined,
  "reason": "Explanation for decision",
  "hookSpecificOutput": {
    "hookEventName": "UserPromptSubmit",
    "additionalContext": "My additional context here"
  }
}

Controllo Decisione Stop/SubagentStop

Gli hooks Stop e SubagentStop possono controllare se Claude deve continuare.

  • "block" previene che Claude si fermi. Devi popolare reason per Claude per sapere come procedere.
  • undefined permette a Claude di fermarsi. reason è ignorato.
{
  "decision": "block" | undefined,
  "reason": "Must be provided when Claude is blocked from stopping"
}

Controllo Decisione SessionStart

Gli hooks SessionStart ti permettono di caricare contesto all’inizio di una sessione.

  • "hookSpecificOutput.additionalContext" aggiunge la stringa al contesto.
  • I valori additionalContext di più hooks vengono concatenati.
{
  "hookSpecificOutput": {
    "hookEventName": "SessionStart",
    "additionalContext": "My additional context here"
  }
}

Controllo Decisione SessionEnd

Gli hooks SessionEnd vengono eseguiti quando una sessione termina. Non possono bloccare la terminazione della sessione ma possono eseguire compiti di pulizia.

Esempio Codice di Uscita: Validazione Comando Bash

#!/usr/bin/env python3
import json
import re
import sys

# Define validation rules as a list of (regex pattern, message) tuples
VALIDATION_RULES = [
    (
        r"\bgrep\b(?!.*\|)",
        "Use 'rg' (ripgrep) instead of 'grep' for better performance and features",
    ),
    (
        r"\bfind\s+\S+\s+-name\b",
        "Use 'rg --files | rg pattern' or 'rg --files -g pattern' instead of 'find -name' for better performance",
    ),
]


def validate_command(command: str) -> list[str]:
    issues = []
    for pattern, message in VALIDATION_RULES:
        if re.search(pattern, command):
            issues.append(message)
    return issues


try:
    input_data = json.load(sys.stdin)
except json.JSONDecodeError as e:
    print(f"Error: Invalid JSON input: {e}", file=sys.stderr)
    sys.exit(1)

tool_name = input_data.get("tool_name", "")
tool_input = input_data.get("tool_input", {})
command = tool_input.get("command", "")

if tool_name != "Bash" or not command:
    sys.exit(1)

# Validate the command
issues = validate_command(command)

if issues:
    for message in issues:
        print(f"• {message}", file=sys.stderr)
    # Exit code 2 blocks tool call and shows stderr to Claude
    sys.exit(2)

Esempio Output JSON: UserPromptSubmit per Aggiungere Contesto e Validazione

Per gli hooks UserPromptSubmit, puoi iniettare contesto usando entrambi i metodi:

  • Codice di uscita 0 con stdout: Claude vede il contesto (caso speciale per UserPromptSubmit)
  • Output JSON: Fornisce più controllo sul comportamento
#!/usr/bin/env python3
import json
import sys
import re
import datetime

# Load input from stdin
try:
    input_data = json.load(sys.stdin)
except json.JSONDecodeError as e:
    print(f"Error: Invalid JSON input: {e}", file=sys.stderr)
    sys.exit(1)

prompt = input_data.get("prompt", "")

# Check for sensitive patterns
sensitive_patterns = [
    (r"(?i)\b(password|secret|key|token)\s*[:=]", "Prompt contains potential secrets"),
]

for pattern, message in sensitive_patterns:
    if re.search(pattern, prompt):
        # Use JSON output to block with a specific reason
        output = {
            "decision": "block",
            "reason": f"Security policy violation: {message}. Please rephrase your request without sensitive information."
        }
        print(json.dumps(output))
        sys.exit(0)

# Add current time to context
context = f"Current time: {datetime.datetime.now()}"
print(context)

"""
The following is also equivalent:
print(json.dumps({
  "hookSpecificOutput": {
    "hookEventName": "UserPromptSubmit",
    "additionalContext": context,
  },
}))
"""

# Allow the prompt to proceed with the additional context
sys.exit(0)

Esempio Output JSON: PreToolUse con Approvazione

#!/usr/bin/env python3
import json
import sys

# Load input from stdin
try:
    input_data = json.load(sys.stdin)
except json.JSONDecodeError as e:
    print(f"Error: Invalid JSON input: {e}", file=sys.stderr)
    sys.exit(1)

tool_name = input_data.get("tool_name", "")
tool_input = input_data.get("tool_input", {})

# Example: Auto-approve file reads for documentation files
if tool_name == "Read":
    file_path = tool_input.get("file_path", "")
    if file_path.endswith((".md", ".mdx", ".txt", ".json")):
        # Use JSON output to auto-approve the tool call
        output = {
            "decision": "approve",
            "reason": "Documentation file auto-approved",
            "suppressOutput": True  # Don't show in transcript mode
        }
        print(json.dumps(output))
        sys.exit(0)

# For other cases, let the normal permission flow proceed
sys.exit(0)

Lavorare con Strumenti MCP

Gli hooks Claude Code funzionano perfettamente con strumenti Model Context Protocol (MCP). Quando i server MCP forniscono strumenti, appaiono con un pattern di denominazione speciale che puoi abbinare nei tuoi hooks.

Denominazione Strumenti MCP

Gli strumenti MCP seguono il pattern mcp__<server>__<tool>, per esempio:

  • mcp__memory__create_entities - Strumento create entities del server Memory
  • mcp__filesystem__read_file - Strumento read file del server Filesystem
  • mcp__github__search_repositories - Strumento search del server GitHub

Configurare Hooks per Strumenti MCP

Puoi targetizzare strumenti MCP specifici o interi server MCP:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "mcp__memory__.*",
        "hooks": [
          {
            "type": "command",
            "command": "echo 'Memory operation initiated' >> ~/mcp-operations.log"
          }
        ]
      },
      {
        "matcher": "mcp__.*__write.*",
        "hooks": [
          {
            "type": "command",
            "command": "/home/user/scripts/validate-mcp-write.py"
          }
        ]
      }
    ]
  }
}

Esempi

Per esempi pratici inclusi formattazione codice, notifiche, e protezione file, vedi Altri Esempi nella guida per iniziare.

Considerazioni di Sicurezza

Disclaimer

USA A TUO RISCHIO: Gli hooks Claude Code eseguono comandi shell arbitrari sul tuo sistema automaticamente. Usando gli hooks, riconosci che:

  • Sei l’unico responsabile per i comandi che configuri
  • Gli hooks possono modificare, eliminare, o accedere a qualsiasi file a cui il tuo account utente può accedere
  • Hooks malevoli o scritti male possono causare perdita di dati o danni al sistema
  • Anthropic non fornisce garanzie e non assume responsabilità per qualsiasi danno risultante dall’uso degli hooks
  • Dovresti testare accuratamente gli hooks in un ambiente sicuro prima dell’uso in produzione

Rivedi sempre e comprendi qualsiasi comando hook prima di aggiungerlo alla tua configurazione.

Migliori Pratiche di Sicurezza

Ecco alcune pratiche chiave per scrivere hooks più sicuri:

  1. Valida e sanifica gli input - Non fidarti mai dei dati di input ciecamente
  2. Quota sempre le variabili shell - Usa "$VAR" non $VAR
  3. Blocca path traversal - Controlla per .. nei percorsi file
  4. Usa percorsi assoluti - Specifica percorsi completi per script (usa $CLAUDE_PROJECT_DIR per il percorso progetto)
  5. Salta file sensibili - Evita .env, .git/, chiavi, ecc.

Sicurezza Configurazione

Le modifiche dirette agli hooks nei file di impostazioni non hanno effetto immediatamente. Claude Code:

  1. Cattura uno snapshot degli hooks all’avvio
  2. Usa questo snapshot durante tutta la sessione
  3. Avvisa se gli hooks sono modificati esternamente
  4. Richiede revisione nel menu /hooks per applicare le modifiche

Questo previene che modifiche malevole agli hooks influenzino la tua sessione corrente.

Dettagli Esecuzione Hook

  • Timeout: Limite di esecuzione di 60 secondi per default, configurabile per comando.
    • Un timeout per un comando individuale non influenza gli altri comandi.
  • Parallelizzazione: Tutti gli hooks corrispondenti vengono eseguiti in parallelo
  • Deduplicazione: Più comandi hook identici vengono deduplicati automaticamente
  • Ambiente: Viene eseguito nella directory corrente con l’ambiente di Claude Code
    • La variabile d’ambiente CLAUDE_PROJECT_DIR è disponibile e contiene il percorso assoluto alla directory radice del progetto (dove Claude Code è stato avviato)
  • Input: JSON via stdin
  • Output:
    • PreToolUse/PostToolUse/Stop/SubagentStop: Progresso mostrato nel transcript (Ctrl-R)
    • Notification/SessionEnd: Registrato solo nel debug (--debug)
    • UserPromptSubmit/SessionStart: stdout aggiunto come contesto per Claude

Debug

Risoluzione Problemi Base

Se i tuoi hooks non funzionano:

  1. Controlla configurazione - Esegui /hooks per vedere se il tuo hook è registrato
  2. Verifica sintassi - Assicurati che le tue impostazioni JSON siano valide
  3. Testa comandi - Esegui prima i comandi hook manualmente
  4. Controlla permessi - Assicurati che gli script siano eseguibili
  5. Rivedi log - Usa claude --debug per vedere dettagli esecuzione hook

Problemi comuni:

  • Virgolette non escapate - Usa \" dentro stringhe JSON
  • Matcher sbagliato - Controlla che i nomi strumento corrispondano esattamente (case-sensitive)
  • Comando non trovato - Usa percorsi completi per script

Debug Avanzato

Per problemi hook complessi:

  1. Ispeziona esecuzione hook - Usa claude --debug per vedere esecuzione hook dettagliata
  2. Valida schemi JSON - Testa input/output hook con strumenti esterni
  3. Controlla variabili d’ambiente - Verifica che l’ambiente di Claude Code sia corretto
  4. Testa casi limite - Prova hooks con percorsi file o input inusuali
  5. Monitora risorse sistema - Controlla per esaurimento risorse durante esecuzione hook
  6. Usa logging strutturato - Implementa logging nei tuoi script hook

Esempio Output Debug

Usa claude --debug per vedere dettagli esecuzione hook:

[DEBUG] Executing hooks for PostToolUse:Write
[DEBUG] Getting matching hook commands for PostToolUse with query: Write
[DEBUG] Found 1 hook matchers in settings
[DEBUG] Matched 1 hooks for query "Write"
[DEBUG] Found 1 hook commands to execute
[DEBUG] Executing hook command: <Your command> with timeout 60000ms
[DEBUG] Hook command completed with status 0: <Your stdout>

I messaggi di progresso appaiono in modalità transcript (Ctrl-R) mostrando:

  • Quale hook sta funzionando
  • Comando in esecuzione
  • Stato successo/fallimento
  • Messaggi di output o errore