Retour au blog

Les Hooks Claude Code : automatiser tes workflows de développement

$date: |read: 9 min|cat: Claude Code
claude-codetutoriel
Les Hooks Claude Code : automatiser tes workflows de développement

#Le problème : Claude fait ce que tu veux... mais pas toujours comme tu veux

Tu utilises Claude Code au quotidien. Il édite tes fichiers, lance des commandes, écrit du code. Mais il y a des choses que tu voudrais voir se produire systématiquement : formatter le code après chaque modification, bloquer certaines commandes dangereuses, recevoir une notification quand Claude attend ton input.

Le souci, c'est que Claude est un LLM — il peut oublier de lancer Prettier, ou décider que ce n'est pas nécessaire. Pour les actions qui doivent toujours se produire, tu as besoin d'un mécanisme déterministe.

C'est exactement le rôle des hooks.

#C'est quoi un hook ?

Un hook est une commande shell qui s'exécute automatiquement à un moment précis du cycle de vie de Claude Code. Contrairement aux instructions dans un CLAUDE.md (que Claude peut interpréter librement), un hook s'exécute à chaque fois, sans exception.

Concrètement, un hook c'est :

  1. Un événement qui déclenche l'exécution (par exemple : "après que Claude a édité un fichier")
  2. Un matcher optionnel qui filtre quand le hook se déclenche (par exemple : "seulement pour l'outil Write")
  3. Un handler — la commande, le prompt ou l'agent qui s'exécute

Les hooks s'exécutent avec les permissions complètes de ton utilisateur système. Ils peuvent lire, modifier ou supprimer n'importe quel fichier auquel tu as accès. Vérifie et teste toujours tes hooks avant de les ajouter.

#Les événements disponibles

Claude Code expose 14 événements dans son cycle de vie. Voici les principaux :

ÉvénementQuand il se déclenchePeut bloquer ?
SessionStartAu démarrage ou reprise de sessionNon
UserPromptSubmitQuand tu envoies un promptOui
PreToolUseAvant l'exécution d'un outilOui
PostToolUseAprès l'exécution réussie d'un outilNon
StopQuand Claude finit de répondreOui
NotificationQuand Claude envoie une notificationNon
SessionEndQuand la session se termineNon

D'autres événements existent pour des cas plus spécifiques : PermissionRequest, PostToolUseFailure, SubagentStart, SubagentStop, TeammateIdle, TaskCompleted et PreCompact.

Le point clé : les événements marqués "Peut bloquer" te permettent d'empêcher une action. Par exemple, un hook PreToolUse peut bloquer une commande rm -rf avant qu'elle ne s'exécute.

#Configurer son premier hook

#La méthode rapide : le menu /hooks

La façon la plus simple de créer un hook est de taper /hooks dans Claude Code. Un menu interactif te guide pour choisir l'événement, le matcher et la commande.

#La méthode manuelle : le fichier settings

Les hooks se configurent dans un fichier JSON. Tu as trois emplacements possibles :

FichierPortéePartageable
~/.claude/settings.jsonTous tes projetsNon
.claude/settings.jsonCe projet uniquementOui (committable)
.claude/settings.local.jsonCe projet uniquementNon (gitignored)

Voici la structure d'une configuration de hook :

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "jq -r '.tool_input.file_path' | xargs npx prettier --write"
          }
        ]
      }
    ]
  }
}

Décortiquons :

  • "PostToolUse" — l'événement : après qu'un outil a été exécuté
  • "matcher": "Edit|Write" — filtre : seulement quand Claude utilise Edit ou Write (c'est une regex)
  • "type": "command" — le type de handler : une commande shell
  • "command" — la commande à exécuter

#Exemples pratiques

#Formatter le code après chaque modification

Le cas d'usage le plus courant. Ce hook lance Prettier sur chaque fichier que Claude modifie :

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "jq -r '.tool_input.file_path' | xargs npx prettier --write"
          }
        ]
      }
    ]
  }
}

Le hook reçoit les données de l'événement en JSON sur stdin. jq extrait le chemin du fichier, et xargs le passe à Prettier.

#Recevoir une notification quand Claude attend

Idéal si tu travailles sur autre chose pendant que Claude réfléchit :

{
  "hooks": {
    "Notification": [
      {
        "matcher": "",
        "hooks": [
          {
            "type": "command",
            "command": "notify-send 'Claude Code' 'Claude a besoin de ton attention'"
          }
        ]
      }
    ]
  }
}

Sur macOS, remplace notify-send par osascript -e 'display notification \"Claude a besoin de ton attention\" with title \"Claude Code\"'.

#Bloquer les commandes destructrices

Ce hook empêche Claude d'exécuter rm -rf :

#!/bin/bash
# .claude/hooks/block-rm.sh
COMMAND=$(jq -r '.tool_input.command')

if echo "$COMMAND" | grep -q 'rm -rf'; then
  jq -n '{
    hookSpecificOutput: {
      hookEventName: "PreToolUse",
      permissionDecision: "deny",
      permissionDecisionReason: "Commande destructrice bloquée par hook"
    }
  }'
else
  exit 0
fi

Et la configuration associée :

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/block-rm.sh"
          }
        ]
      }
    ]
  }
}

N'oublie pas de rendre le script exécutable avec chmod +x .claude/hooks/block-rm.sh.

#Protéger des fichiers sensibles

Ce hook empêche Claude de modifier .env, package-lock.json et tout ce qui est dans .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/")

for pattern in "${PROTECTED_PATTERNS[@]}"; do
  if [[ "$FILE_PATH" == *"$pattern"* ]]; then
    echo "Bloqué : $FILE_PATH correspond au pattern protégé '$pattern'" >&2
    exit 2
  fi
done

exit 0

Le code de sortie exit 2 est la convention pour bloquer une action. Le message envoyé sur stderr est transmis à Claude comme feedback pour qu'il adapte son approche.

#Comment un hook communique avec Claude Code

Les hooks échangent des données via trois canaux :

#Entrée (stdin)

Quand un événement se déclenche, Claude Code envoie un JSON sur stdin avec le contexte :

{
  "session_id": "abc123",
  "cwd": "/home/user/mon-projet",
  "hook_event_name": "PreToolUse",
  "tool_name": "Bash",
  "tool_input": {
    "command": "npm test"
  }
}

Les champs varient selon l'événement. Un hook PostToolUse recevra en plus un champ tool_response avec le résultat de l'outil.

#Sortie (exit code + stdout/stderr)

  • Exit 0 : tout va bien, l'action continue. Si tu écris du JSON sur stdout, Claude Code le lit pour des contrôles avancés
  • Exit 2 : l'action est bloquée. Le texte sur stderr est renvoyé à Claude comme feedback
  • Autre exit code : erreur non-bloquante, visible en mode verbose (Ctrl+O)

#Contrôle avancé via JSON

Pour les hooks PreToolUse, tu peux retourner un JSON structuré avec trois options :

  • "allow" : autorise sans demander de permission
  • "deny" : bloque l'action et explique pourquoi à Claude
  • "ask" : affiche le dialogue de permission à l'utilisateur
{
  "hookSpecificOutput": {
    "hookEventName": "PreToolUse",
    "permissionDecision": "deny",
    "permissionDecisionReason": "Les écritures en base de données ne sont pas autorisées"
  }
}

#Les trois types de hooks

#Command hooks

Le type le plus courant. Tu écris un script shell qui reçoit du JSON et retourne un exit code :

{
  "type": "command",
  "command": "./mon-script.sh",
  "timeout": 30
}

#Prompt hooks

Au lieu d'un script, tu envoies un prompt à un modèle Claude (Haiku par défaut) qui retourne une décision oui/non :

{
  "type": "prompt",
  "prompt": "Vérifie si toutes les tâches demandées sont terminées. $ARGUMENTS"
}

Le modèle répond avec {"ok": true} ou {"ok": false, "reason": "ce qui reste à faire"}. C'est utile pour des décisions qui nécessitent du jugement plutôt que des règles fixes.

#Agent hooks

Comme les prompt hooks, mais l'agent peut utiliser des outils (Read, Grep, Glob) pour vérifier des conditions avant de décider :

{
  "type": "agent",
  "prompt": "Vérifie que tous les tests unitaires passent avant de terminer. $ARGUMENTS",
  "timeout": 120
}

Utilise les command hooks pour les règles déterministes (formatter, bloquer). Utilise les prompt hooks pour les décisions qui nécessitent du jugement. Utilise les agent hooks quand la vérification nécessite d'inspecter des fichiers.

#Hooks dans les Skills et Subagents

Les hooks ne sont pas limités aux fichiers settings. Tu peux aussi les définir directement dans le frontmatter d'un Skill ou d'un subagent :

---
name: secure-operations
description: Opérations avec vérifications de sécurité
hooks:
  PreToolUse:
    - matcher: "Bash"
      hooks:
        - type: command
          command: "./scripts/security-check.sh"
---

Ces hooks sont scopés au cycle de vie du composant : ils s'activent quand le Skill ou l'agent démarre, et se désactivent quand il termine.

#Hooks asynchrones

Par défaut, un hook bloque Claude jusqu'à sa complétion. Pour les tâches longues (tests, déploiements), tu peux ajouter "async": true :

{
  "type": "command",
  "command": "./run-tests.sh",
  "async": true,
  "timeout": 300
}

Claude continue à travailler pendant que le hook s'exécute en arrière-plan. Quand le hook termine, son résultat est renvoyé à Claude au prochain tour de conversation.

Les hooks asynchrones ne peuvent pas bloquer les actions. Au moment où ils terminent, l'action qui les a déclenchés est déjà passée. Ils sont utiles pour du monitoring ou des tests en arrière-plan, pas pour de la validation.

#Déboguer ses hooks

Deux techniques pour diagnostiquer les problèmes :

  1. Mode verbose (Ctrl+O) : affiche la sortie des hooks dans le transcript
  2. Mode debug (claude --debug) : montre les détails d'exécution, les matchers qui ont matché, les codes de sortie

Tu peux aussi tester un hook manuellement :

echo '{"tool_name":"Bash","tool_input":{"command":"ls"}}' | ./mon-hook.sh
echo $?

#Pièges courants

  • Le hook ne se déclenche pas : vérifie que le matcher est correct (sensible à la casse) et que tu as le bon événement
  • Erreur JSON : si ton ~/.zshrc affiche du texte au démarrage, ça peut polluer le stdout du hook. Entoure tes echo avec if [[ $- == *i* ]]; then ... fi
  • Boucle infinie avec Stop : ton hook Stop doit vérifier le champ stop_hook_active et sortir avec exit 0 s'il est à true

#Récapitulatif

Les hooks ajoutent une couche de contrôle déterministe à Claude Code. Là où les instructions dans CLAUDE.md sont des suggestions que Claude peut interpréter, les hooks sont des règles qui s'exécutent systématiquement.

Les cas d'usage les plus fréquents :

  • Formatage automatique : Prettier, Black, ou ton formatter après chaque édition
  • Sécurité : bloquer les commandes dangereuses, protéger les fichiers sensibles
  • Notifications : être alerté quand Claude attend ton input
  • Contexte : réinjecter des informations après une compaction de contexte
  • Qualité : vérifier que les tests passent avant de considérer une tâche terminée

Pour aller plus loin, consulte la documentation officielle des hooks et le guide pratique qui couvre les exemples avancés et le troubleshooting.