- Avancé
- Hooks
Qu'est-ce qu'un hook ?
Les hooks sont des commandes shell déclenchées automatiquement par Claude Code à des moments précis du cycle d'exécution. Pensez-y comme des écouteurs d'événements au niveau du terminal : Claude effectue une action, votre hook réagit.
Contrairement aux MCP (qui étendent les capacités de Claude) ou aux Skills (qui définissent des comportements), les hooks interceptent le flux d'exécution pour y injecter une logique externe.
Les types de hooks
Claude Code expose un cycle de vie riche avec 32 événements d'ancrage. Les six premiers couvrent 90 % des usages courants :
| Type | Déclenchement | Usage typique |
|---|---|---|
SessionStart | Au démarrage d'une session | Vérifier les prérequis, charger une config |
PreToolUse | Avant l'exécution d'un outil | Valider les paramètres, bloquer des actions |
PostToolUse | Après l'exécution d'un outil | Auto-format, notifications, logs |
Stop | Quand Claude termine la session | Rapport de session, nettoyage |
Notification | Quand Claude envoie une notification | Alertes Slack, emails |
PermissionRequest | Avant qu'une permission soit demandée | Auto-approuver certaines actions, router vers un modèle |
La référence exhaustive des 32 événements (utiles pour les power users : subagents, worktrees, compaction, channels) est documentée plus bas, dans la section Référence complète.
Configuration dans settings.json
Les hooks se déclarent dans votre fichier settings.json (~/.claude/settings.json pour global, ou .claude/settings.json pour un projet) :
{"hooks": {"PreToolUse": [{"matcher": "Bash","hooks": [{"type": "command","command": "echo 'Validation avant bash...' && exit 0"}]}],"PostToolUse": [{"matcher": "Write","hooks": [{"type": "command","command": "prettier --write \"$CLAUDE_TOOL_OUTPUT_FILE\" 2>/dev/null || true"}]}],"Stop": [{"hooks": [{"type": "command","command": "echo 'Session terminée' >> ~/.claude/session.log"}]}]}}
Structure d'un hook
hooks:
<TypeDeHook>: # PreToolUse | PostToolUse | Stop | Notification
- matcher: "<outil>" # Nom de l'outil à intercepter (optionnel pour Stop/Notification)
hooks:
- type: "command" # Seul type disponible actuellement
command: "..." # Commande shell exécutée
Le matcher accepte le nom exact d'un outil Claude Code : Bash, Write, Edit, Read, WebFetch, etc. Il est omis pour Stop, Notification et SessionStart.
Variables d'environnement disponibles
Vos commandes de hook reçoivent plusieurs variables d'environnement injectées par Claude Code :
| Variable | Disponible dans | Contenu |
|---|---|---|
CLAUDE_TOOL_NAME | PreToolUse, PostToolUse | Nom de l'outil (Bash, Write, etc.) |
CLAUDE_TOOL_INPUT | PreToolUse | Paramètres JSON de l'outil |
CLAUDE_TOOL_OUTPUT | PostToolUse | Résultat de l'outil (stdout) |
CLAUDE_TOOL_OUTPUT_FILE | PostToolUse (Write, Edit) | Chemin du fichier écrit ou modifié |
CLAUDE_SESSION_ID | Tous | Identifiant unique de la session |
Exemples concrets
PreToolUse : valider les paramètres d'une commande shell
Ce hook bloque toute commande rm -rf sur des chemins dangereux :
{"hooks": {"PreToolUse": [{"matcher": "Bash","hooks": [{"type": "command","command": "echo $CLAUDE_TOOL_INPUT | python3 -c \"import sys, json; cmd = json.load(sys.stdin).get('command', ''); exit(1 if 'rm -rf /' in cmd else 0)\""}]}]}}
PostToolUse : auto-format avec Prettier
Formate automatiquement chaque fichier écrit par Claude Code :
{"hooks": {"PostToolUse": [{"matcher": "Write","hooks": [{"type": "command","command": "[ -f \"$CLAUDE_TOOL_OUTPUT_FILE\" ] && npx prettier --write \"$CLAUDE_TOOL_OUTPUT_FILE\" 2>/dev/null || true"}]},{"matcher": "Edit","hooks": [{"type": "command","command": "[ -f \"$CLAUDE_TOOL_OUTPUT_FILE\" ] && npx prettier --write \"$CLAUDE_TOOL_OUTPUT_FILE\" 2>/dev/null || true"}]}]}}
PostToolUse : notification Slack après un commit
Créez d'abord le script de notification :
#!/bin/bash# ~/.claude/hooks/notify-slack.shTOOL_INPUT=$(echo "$CLAUDE_TOOL_INPUT" | python3 -c "import sys, json; d = json.load(sys.stdin); print(d.get('command', ''))")# Déclenche uniquement sur git commitif echo "$TOOL_INPUT" | grep -q "git commit"; thenCOMMIT_MSG=$(git log --oneline -1 2>/dev/null || echo "Commit créé")curl -s -X POST "$SLACK_WEBHOOK_URL" \-H 'Content-type: application/json' \-d "{\"text\": \":white_check_mark: Claude Code a créé un commit : \`$COMMIT_MSG\`\"}" > /dev/nullfiexit 0
Puis configurez le hook dans settings.json :
{"hooks": {"PostToolUse": [{"matcher": "Bash","hooks": [{"type": "command","command": "bash ~/.claude/hooks/notify-slack.sh"}]}]}}
Stop : générer un rapport de session
Créez le script de rapport :
#!/bin/bash# ~/.claude/hooks/session-report.shLOG_FILE=~/.claude/sessions/$(date +%Y-%m-%d).logmkdir -p ~/.claude/sessions{echo "=== Session $(date '+%Y-%m-%d %H:%M:%S') ==="echo "Session ID: $CLAUDE_SESSION_ID"echo "Répertoire: $(pwd)"echo "Branche Git: $(git branch --show-current 2>/dev/null || echo 'N/A')"echo ""} >> "$LOG_FILE"exit 0
Puis dans settings.json :
{"hooks": {"Stop": [{"hooks": [{"type": "command","command": "bash ~/.claude/hooks/session-report.sh"}]}]}}
SessionStart : vérifier les prérequis au démarrage
Ce hook vérifie que Docker est en cours d'exécution avant de commencer une session de développement. Si Docker est arrêté, Claude en est informé dès le départ plutôt qu'au milieu d'une tâche.
#!/bin/bash# ~/.claude/hooks/check-prerequisites.sh# Vérifier que Docker est lancéif ! docker info > /dev/null 2>&1; thenecho "AVERTISSEMENT : Docker n'est pas lancé. Les commandes liées aux conteneurs échoueront." >&2fi# Afficher le contexte du projet au démarrageecho "Projet : $(basename $(pwd))"echo "Branche : $(git branch --show-current 2>/dev/null || echo 'N/A')"echo "Node : $(node --version 2>/dev/null || echo 'Non installé')"exit 0
Puis dans settings.json :
{"hooks": {"SessionStart": [{"hooks": [{"type": "command","command": "bash ~/.claude/hooks/check-prerequisites.sh"}]}]}}
PermissionRequest : gérer les approbations automatiquement
Le hook PermissionRequest s'exécute quand Claude demande une permission à l'utilisateur. Un code de sortie 0 auto-approuve, un code 1 force la confirmation manuelle.
Ce script auto-approuve les commandes de lecture Git (inoffensives) et bloque les commandes d'écriture réseau pour demander confirmation :
#!/bin/bash# ~/.claude/hooks/smart-permissions.shTOOL=$(echo "$CLAUDE_TOOL_INPUT" | python3 -c "import sys, jsond = json.load(sys.stdin)print(d.get('tool_name', ''))" 2>/dev/null)COMMAND=$(echo "$CLAUDE_TOOL_INPUT" | python3 -c "import sys, jsond = json.load(sys.stdin)print(d.get('command', ''))" 2>/dev/null)# Auto-approuver les commandes Git en lecture seuleSAFE_GIT_CMDS="git status|git diff|git log|git branch|git show"if echo "$COMMAND" | grep -qE "$SAFE_GIT_CMDS"; thenexit 0 # Approuvé automatiquementfi# Demander confirmation pour git push et git forceif echo "$COMMAND" | grep -qE "git push|git force|git reset --hard"; thenexit 1 # Demande confirmation manuellefi# Par défaut : laisser Claude Code gérerexit 0
{"hooks": {"PermissionRequest": [{"hooks": [{"type": "command","command": "bash ~/.claude/hooks/smart-permissions.sh"}]}]}}
Patterns avancés
Hooks conditionnels par type de fichier
N'appliquer Prettier que sur les fichiers TypeScript et JavaScript :
#!/bin/bash# ~/.claude/hooks/format-if-ts.shFILE="$CLAUDE_TOOL_OUTPUT_FILE"if [[ "$FILE" =~ \.(ts|tsx|js|jsx|json|css|scss)$ ]]; thennpx prettier --write "$FILE" 2>/dev/nullfiexit 0
Hooks chaînés : lint + format + test
Exécute une chaîne de vérifications après chaque écriture de fichier :
#!/bin/bash# ~/.claude/hooks/quality-chain.shFILE="$CLAUDE_TOOL_OUTPUT_FILE"if [[ -z "$FILE" ]] || [[ ! -f "$FILE" ]]; thenexit 0fi# Étape 1 : Formatnpx prettier --write "$FILE" 2>/dev/null || true# Étape 2 : Lint (sans bloquer)npx eslint --fix "$FILE" 2>/dev/null || true# Étape 3 : Vérification TypeScript (sans bloquer)if [[ "$FILE" =~ \.(ts|tsx)$ ]]; thennpx tsc --noEmit 2>/dev/null || truefiexit 0
Hook de validation de secrets
Bloque toute écriture contenant des patterns de secrets :
#!/bin/bash# ~/.claude/hooks/check-secrets.sh# Hook PreToolUse sur Write/EditFILE_CONTENT=$(echo "$CLAUDE_TOOL_INPUT" | python3 -c "import sys, jsond = json.load(sys.stdin)print(d.get('content', '') + d.get('new_string', ''))" 2>/dev/null)# Patterns à bloquer (clés API courantes)PATTERNS=("sk-[a-zA-Z0-9]{48}""AKIA[0-9A-Z]{16}""AIza[0-9A-Za-z-_]{35}""ghp_[a-zA-Z0-9]{36}")for pattern in "${PATTERNS[@]}"; doif echo "$FILE_CONTENT" | grep -qE "$pattern"; thenecho "ERREUR : Secret détecté dans le contenu (pattern: $pattern)" >&2exit 1fidoneexit 0
Routing de modèle selon la complexité
Ce pattern utilise un hook PreToolUse pour rediriger les outils de lecture simples (grep, lecture de fichier) vers Haiku, moins coûteux, et réserver Opus aux tâches complexes. Claude Code lit la sortie du hook pour adapter le modèle utilisé.
{"hooks": {"PreToolUse": [{"matcher": "Read|Glob|Grep","hooks": [{"type": "command","command": "echo 'model: haiku'"}]}]}}
Nudge de continuation après Stop
Claude s'arrête parfois prématurément sans avoir vérifié son travail. Ce hook Stop relance automatiquement une invite de vérification :
#!/bin/bash# ~/.claude/hooks/nudge-continue.sh# Vérifie si la session s'est arrêtée sans avoir lancé les testsLAST_OUTPUT=$(cat ~/.claude/last_output.txt 2>/dev/null || echo "")if ! echo "$LAST_OUTPUT" | grep -qE "test|lint|check|verify"; thenecho "Rappel : avez-vous vérifié que les tests passent et que le lint est propre ?"fiexit 0
{"hooks": {"Stop": [{"hooks": [{"type": "command","command": "bash ~/.claude/hooks/nudge-continue.sh"}]}]}}
Logging structuré en JSON
Auditer chaque action de Claude Code dans un fichier JSON facilite l'analyse post-session (quels fichiers ont été modifiés, quelles commandes ont été exécutées, combien de temps) :
#!/bin/bash# ~/.claude/hooks/audit-log.sh# Hook PostToolUse (tous les outils)LOG_FILE=~/.claude/audit/$(date +%Y-%m-%d).jsonlmkdir -p ~/.claude/audit# Construire l'entrée JSONpython3 -c "import sys, json, osfrom datetime import datetimeentry = {'timestamp': datetime.utcnow().isoformat() + 'Z','session_id': os.environ.get('CLAUDE_SESSION_ID', ''),'tool': os.environ.get('CLAUDE_TOOL_NAME', ''),'file': os.environ.get('CLAUDE_TOOL_OUTPUT_FILE', ''),'success': True}print(json.dumps(entry))" >> "$LOG_FILE"exit 0
{"hooks": {"PostToolUse": [{"hooks": [{"type": "command","command": "bash ~/.claude/hooks/audit-log.sh"}]}]}}
Le fichier ~/.claude/audit/2026-03-30.jsonl contiendra une ligne JSON par action, facile à analyser avec jq :
# Lister tous les fichiers modifiés aujourd'huicat ~/.claude/audit/$(date +%Y-%m-%d).jsonl | jq -r 'select(.file != "") | .file' | sort -u
Hooks on-demand via slash commands
Les slash commands peuvent activer ou désactiver des hooks à la volée. C'est utile pour basculer entre un mode "prudent" et un mode "rapide" selon la nature du travail.
Créez deux scripts dans ~/.claude/commands/ :
#!/bin/bash# ~/.claude/commands/careful.sh# Activé par /careful — bloque les commandes destructricesCOMMAND=$(echo "$CLAUDE_TOOL_INPUT" | python3 -c "import sys, jsond = json.load(sys.stdin)print(d.get('command', ''))" 2>/dev/null)DANGEROUS_PATTERNS="rm -rf|DROP TABLE|DELETE FROM|truncate|format [A-Z]:"if echo "$COMMAND" | grep -qiE "$DANGEROUS_PATTERNS"; thenecho "BLOQUÉ par le mode /careful : commande potentiellement destructrice détectée." >&2exit 1fiexit 0
#!/bin/bash# ~/.claude/commands/freeze.sh# Activé par /freeze — bloque les éditions hors du dossier courantFILE="$CLAUDE_TOOL_OUTPUT_FILE"ALLOWED_DIR="$(pwd)"if [[ -n "$FILE" ]] && [[ "$FILE" != "$ALLOWED_DIR"* ]]; thenecho "BLOQUÉ par le mode /freeze : édition hors de $ALLOWED_DIR interdite." >&2exit 1fiexit 0
Configurez-les comme hooks conditionnels dans settings.json :
{"hooks": {"PreToolUse": [{"matcher": "Bash","hooks": [{"type": "command","command": "[ -f ~/.claude/.careful ] && bash ~/.claude/commands/careful.sh || true"}]},{"matcher": "Write|Edit","hooks": [{"type": "command","command": "[ -f ~/.claude/.freeze ] && bash ~/.claude/commands/freeze.sh || true"}]}]}}
Créez ensuite les slash commands pour activer/désactiver ces modes :
# ~/.claude/commands/careful.mdActive le mode prudent : bloque les commandes destructrices (rm -rf, DROP TABLE, etc.).touch ~/.claude/.carefulecho "Mode /careful activé. Les commandes destructrices seront bloquées."
# ~/.claude/commands/freeze.mdGèle les modifications hors du dossier courant.touch ~/.claude/.freezeecho "Mode /freeze activé. Les éditions sont limitées à $(pwd)."
Pour désactiver : /uncareful et /unfreeze qui suppriment les fichiers marqueurs avec rm -f ~/.claude/.careful.
Troubleshooting
Mon hook ne se déclenche pas
- Vérifiez le nom exact du matcher (sensible à la casse :
Bash, pasbash) - Rechargez Claude Code : fermez et rouvrez la session
- Validez le JSON de votre
settings.json:
cat ~/.claude/settings.json | python3 -m json.tool
Mon hook bloque toutes les actions
Un exit 1 inattendu peut bloquer Claude. Ajoutez un || true sur les commandes non critiques :
# Mauvais : peut bloquer si npx n'est pas installénpx prettier --write "$FILE"# Bon : ne bloque pas même en cas d'erreurnpx prettier --write "$FILE" 2>/dev/null || true
Déboguer un hook
Loggez la sortie de votre hook pour analyser son comportement :
#!/bin/bash# Ajoutez ces lignes en début de script pour le debugexec 2>> ~/.claude/hooks.logset -x # Active le mode trace (chaque commande est loggée)# Votre code de hook...
Le hook est trop lent
Les hooks bloquent l'exécution de Claude Code. Pour des opérations longues, utilisez l'exécution en arrière-plan :
#!/bin/bash# Lance la notification de manière asynchrone(curl -s -X POST "$SLACK_WEBHOOK_URL" -d '{"text":"Done!"}' > /dev/null 2>&1) &exit 0 # Retourne immédiatement
Référence complète des 32 événements
La liste exhaustive des événements de hooks groupée par phase du cycle de vie. La plupart des événements partagent le même format de configuration (matcher + command) et reçoivent les mêmes variables d'environnement de base (CLAUDE_SESSION_ID).
Cycle de session
| Événement | Description |
|---|---|
Setup | Au tout premier démarrage (avant SessionStart). Idéal pour télécharger des assets, vérifier l'auth |
SessionStart | Démarrage d'une session interactive |
SessionEnd | Fin d'une session (terminée normalement, distinct de Stop) |
Stop | Claude termine son tour ou la session |
ConfigChange | Modification de settings.json ou des permissions à chaud |
Outils et exécution
| Événement | Description |
|---|---|
PreToolUse | Avant l'exécution d'un outil (Bash, Write, etc.) |
PostToolUse | Après l'exécution d'un outil |
PermissionRequest | Avant qu'une demande de permission n'arrive à l'utilisateur |
UserPromptSubmit | Soumission d'un prompt utilisateur (avant que Claude ne traite) |
Notification | Émission d'une notification (push desktop, channels) |
Subagents et orchestration
| Événement | Description |
|---|---|
SubagentStart | Démarrage d'un subagent (via Agent tool) |
SubagentStop | Fin d'exécution d'un subagent |
TeammateIdle | Un teammate (en mode Agent Teams) devient inactif |
TaskCompleted | Une tâche du système /tasks est marquée complétée |
Compaction et contexte
| Événement | Description |
|---|---|
PreCompact | Avant l'auto-compaction du contexte |
PostCompact | Après l'auto-compaction (résumé disponible dans la sortie) |
ContextThreshold | Seuil de remplissage atteint (configurable, ex : 50 %, 80 %) |
Worktrees et fichiers
| Événement | Description |
|---|---|
WorktreeCreate | Création d'un worktree (claude -w ou isolation: worktree) |
WorktreeRemove | Suppression d'un worktree |
FileWatcherTrigger | Modification d'un fichier suivi (en mode watch) |
MCPServerConnect | Connexion établie avec un MCP server |
MCPServerDisconnect | Déconnexion d'un MCP server |
Routines et planification cloud
| Événement | Description |
|---|---|
RoutineStart | Lancement d'une routine cloud (cron /schedule) |
RoutineComplete | Fin d'une routine cloud |
LoopIteration | Tour d'une /loop locale |
Erreurs et observabilité
| Événement | Description |
|---|---|
ErrorOccurred | Erreur non récupérée par Claude (utile pour Sentry) |
RateLimitHit | Limite de tokens ou de requêtes atteinte |
CostThreshold | Seuil de coût atteint (--max-budget-usd) |
Channels (push events)
| Événement | Description |
|---|---|
ChannelMessage | Réception d'un message Telegram/Discord/Slack via channels |
ChannelEvent | Évenement non-message d'un channel |
Réservés / expérimentaux
| Événement | Description |
|---|---|
Heartbeat | Tick périodique (1/min, expérimental) |
BackgroundAgentStart | Démarrage d'un Background Agent |
BackgroundAgentStop | Fin d'un Background Agent |
Configuration team vs perso
Avec autant d'événements, la séparation team/perso devient critique. Claude Code lit deux fichiers de hooks dans cet ordre :
| Fichier | Portée | Versionné ? |
|---|---|---|
.claude/hooks-config.json | Équipe (commit dans le repo) | Oui |
.claude/hooks-config.local.json | Personnel (gitignored) | Non |
Les hooks personnels (TTS, sons, audit local) vont dans .local. Les hooks d'équipe (lint enforcement, secret scan, audit log centralisé) vont dans le fichier versionné.
# .gitignore.claude/hooks-config.local.json
Prochaines étapes
Les hooks sont puissants en autonome, mais atteignent leur plein potentiel combinés au mode headless pour les pipelines CI/CD.