Aller au contenu principal
Avancé

Système de Hooks

Automatisez vos workflows Claude Code avec les 32 événements de hooks (PreToolUse, PostToolUse, SubagentStart, PermissionRequest, WorktreeCreate, etc.). Auto-format, notifications, rapports de session, patterns avancés.

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 :

TypeDéclenchementUsage typique
SessionStartAu démarrage d'une sessionVérifier les prérequis, charger une config
PreToolUseAvant l'exécution d'un outilValider les paramètres, bloquer des actions
PostToolUseAprès l'exécution d'un outilAuto-format, notifications, logs
StopQuand Claude termine la sessionRapport de session, nettoyage
NotificationQuand Claude envoie une notificationAlertes Slack, emails
PermissionRequestAvant qu'une permission soit demandéeAuto-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 :

VariableDisponible dansContenu
CLAUDE_TOOL_NAMEPreToolUse, PostToolUseNom de l'outil (Bash, Write, etc.)
CLAUDE_TOOL_INPUTPreToolUseParamètres JSON de l'outil
CLAUDE_TOOL_OUTPUTPostToolUseRésultat de l'outil (stdout)
CLAUDE_TOOL_OUTPUT_FILEPostToolUse (Write, Edit)Chemin du fichier écrit ou modifié
CLAUDE_SESSION_IDTousIdentifiant 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.sh
TOOL_INPUT=$(echo "$CLAUDE_TOOL_INPUT" | python3 -c "import sys, json; d = json.load(sys.stdin); print(d.get('command', ''))")
# Déclenche uniquement sur git commit
if echo "$TOOL_INPUT" | grep -q "git commit"; then
COMMIT_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/null
fi
exit 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.sh
LOG_FILE=~/.claude/sessions/$(date +%Y-%m-%d).log
mkdir -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; then
echo "AVERTISSEMENT : Docker n'est pas lancé. Les commandes liées aux conteneurs échoueront." >&2
fi
# Afficher le contexte du projet au démarrage
echo "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.sh
TOOL=$(echo "$CLAUDE_TOOL_INPUT" | python3 -c "
import sys, json
d = json.load(sys.stdin)
print(d.get('tool_name', ''))
" 2>/dev/null)
COMMAND=$(echo "$CLAUDE_TOOL_INPUT" | python3 -c "
import sys, json
d = json.load(sys.stdin)
print(d.get('command', ''))
" 2>/dev/null)
# Auto-approuver les commandes Git en lecture seule
SAFE_GIT_CMDS="git status|git diff|git log|git branch|git show"
if echo "$COMMAND" | grep -qE "$SAFE_GIT_CMDS"; then
exit 0 # Approuvé automatiquement
fi
# Demander confirmation pour git push et git force
if echo "$COMMAND" | grep -qE "git push|git force|git reset --hard"; then
exit 1 # Demande confirmation manuelle
fi
# Par défaut : laisser Claude Code gérer
exit 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.sh
FILE="$CLAUDE_TOOL_OUTPUT_FILE"
if [[ "$FILE" =~ \.(ts|tsx|js|jsx|json|css|scss)$ ]]; then
npx prettier --write "$FILE" 2>/dev/null
fi
exit 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.sh
FILE="$CLAUDE_TOOL_OUTPUT_FILE"
if [[ -z "$FILE" ]] || [[ ! -f "$FILE" ]]; then
exit 0
fi
# Étape 1 : Format
npx 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)$ ]]; then
npx tsc --noEmit 2>/dev/null || true
fi
exit 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/Edit
FILE_CONTENT=$(echo "$CLAUDE_TOOL_INPUT" | python3 -c "
import sys, json
d = 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[@]}"; do
if echo "$FILE_CONTENT" | grep -qE "$pattern"; then
echo "ERREUR : Secret détecté dans le contenu (pattern: $pattern)" >&2
exit 1
fi
done
exit 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 tests
LAST_OUTPUT=$(cat ~/.claude/last_output.txt 2>/dev/null || echo "")
if ! echo "$LAST_OUTPUT" | grep -qE "test|lint|check|verify"; then
echo "Rappel : avez-vous vérifié que les tests passent et que le lint est propre ?"
fi
exit 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).jsonl
mkdir -p ~/.claude/audit
# Construire l'entrée JSON
python3 -c "
import sys, json, os
from datetime import datetime
entry = {
'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'hui
cat ~/.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 destructrices
COMMAND=$(echo "$CLAUDE_TOOL_INPUT" | python3 -c "
import sys, json
d = 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"; then
echo "BLOQUÉ par le mode /careful : commande potentiellement destructrice détectée." >&2
exit 1
fi
exit 0
#!/bin/bash
# ~/.claude/commands/freeze.sh
# Activé par /freeze — bloque les éditions hors du dossier courant
FILE="$CLAUDE_TOOL_OUTPUT_FILE"
ALLOWED_DIR="$(pwd)"
if [[ -n "$FILE" ]] && [[ "$FILE" != "$ALLOWED_DIR"* ]]; then
echo "BLOQUÉ par le mode /freeze : édition hors de $ALLOWED_DIR interdite." >&2
exit 1
fi
exit 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.md
Active le mode prudent : bloque les commandes destructrices (rm -rf, DROP TABLE, etc.).
touch ~/.claude/.careful
echo "Mode /careful activé. Les commandes destructrices seront bloquées."
# ~/.claude/commands/freeze.md
Gèle les modifications hors du dossier courant.
touch ~/.claude/.freeze
echo "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

  1. Vérifiez le nom exact du matcher (sensible à la casse : Bash, pas bash)
  2. Rechargez Claude Code : fermez et rouvrez la session
  3. 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'erreur
npx 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 debug
exec 2>> ~/.claude/hooks.log
set -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énementDescription
SetupAu tout premier démarrage (avant SessionStart). Idéal pour télécharger des assets, vérifier l'auth
SessionStartDémarrage d'une session interactive
SessionEndFin d'une session (terminée normalement, distinct de Stop)
StopClaude termine son tour ou la session
ConfigChangeModification de settings.json ou des permissions à chaud

Outils et exécution

ÉvénementDescription
PreToolUseAvant l'exécution d'un outil (Bash, Write, etc.)
PostToolUseAprès l'exécution d'un outil
PermissionRequestAvant qu'une demande de permission n'arrive à l'utilisateur
UserPromptSubmitSoumission d'un prompt utilisateur (avant que Claude ne traite)
NotificationÉmission d'une notification (push desktop, channels)

Subagents et orchestration

ÉvénementDescription
SubagentStartDémarrage d'un subagent (via Agent tool)
SubagentStopFin d'exécution d'un subagent
TeammateIdleUn teammate (en mode Agent Teams) devient inactif
TaskCompletedUne tâche du système /tasks est marquée complétée

Compaction et contexte

ÉvénementDescription
PreCompactAvant l'auto-compaction du contexte
PostCompactAprès l'auto-compaction (résumé disponible dans la sortie)
ContextThresholdSeuil de remplissage atteint (configurable, ex : 50 %, 80 %)

Worktrees et fichiers

ÉvénementDescription
WorktreeCreateCréation d'un worktree (claude -w ou isolation: worktree)
WorktreeRemoveSuppression d'un worktree
FileWatcherTriggerModification d'un fichier suivi (en mode watch)
MCPServerConnectConnexion établie avec un MCP server
MCPServerDisconnectDéconnexion d'un MCP server

Routines et planification cloud

ÉvénementDescription
RoutineStartLancement d'une routine cloud (cron /schedule)
RoutineCompleteFin d'une routine cloud
LoopIterationTour d'une /loop locale

Erreurs et observabilité

ÉvénementDescription
ErrorOccurredErreur non récupérée par Claude (utile pour Sentry)
RateLimitHitLimite de tokens ou de requêtes atteinte
CostThresholdSeuil de coût atteint (--max-budget-usd)

Channels (push events)

ÉvénementDescription
ChannelMessageRéception d'un message Telegram/Discord/Slack via channels
ChannelEventÉvenement non-message d'un channel

Réservés / expérimentaux

ÉvénementDescription
HeartbeatTick périodique (1/min, expérimental)
BackgroundAgentStartDémarrage d'un Background Agent
BackgroundAgentStopFin 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 :

FichierPortéeVersionné ?
.claude/hooks-config.jsonÉquipe (commit dans le repo)Oui
.claude/hooks-config.local.jsonPersonnel (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.