Tous les articles

Hooks Claude Code : exemples qui bloquent, avec tests

Les hooks Claude Code sont des commandes qui s’exécutent à des moments précis d’une session : avant l’exécution d’un outil, après son exécution, à la soumission d’un prompt, à l’arrêt de la session. C’est le moyen de transformer « l’agent ne doit pas faire ça » en quelque chose de réellement vérifié. Cet article présente des exemples de hooks Claude Code qui bloquent ce qui doit être bloqué, comment les tester, et les erreurs derrière la plupart des recherches « hook error ».

Tout ce qui suit s’appuie sur la référence officielle des hooks et la documentation des permissions. Nous utilisons Claude Code avec des hooks tous les jours pour notre studio ; l’un des nôtres est un hook Stop qui avertit quand la consommation de tokens d’une session dépasse une limite.

Où sont configurés les hooks Claude Code

Les hooks vivent dans les fichiers de paramètres :

Fichier S’applique à Partagé
~/.claude/settings.json tous vos projets non
.claude/settings.json un projet oui, peut être versionné
.claude/settings.local.json un projet non

Les entrées de ces fichiers sont fusionnées, pas remplacées. Les plugins, les skills et les sous-agents peuvent aussi apporter leurs propres hooks.

Une entrée de hook a trois niveaux : l’événement, un groupe matcher, et les handlers :

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          { "type": "command", "command": "python3 ${CLAUDE_PROJECT_DIR}/.claude/hooks/block_rm.py" }
        ]
      }
    ]
  }
}

CLAUDE_PROJECT_DIR est la racine du projet où la session a démarré. Sous Windows, appelez Python comme votre système l’attend.

Un hook PreToolUse qui bloque une commande risquée

Un hook PreToolUse reçoit l’appel d’outil prévu en JSON sur l’entrée standard : tool_name, tool_input et quelques champs de session. Pour Bash, tool_input.command est le texte de la commande.

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

data = json.load(sys.stdin)
cmd = data.get("tool_input", {}).get("command", "")

if re.search(r"\brm\s+-\w*(rf|fr)\w*\b", cmd):
    print("Blocked: recursive force delete. Ask the user to run it.", file=sys.stderr)
    sys.exit(2)
sys.exit(0)

La règle qui compte : le code de sortie 2 bloque l’appel, et stderr devient la raison que Claude voit. Le code 0 laisse passer. Tout autre code est une erreur non bloquante : l’action a lieu et vous recevez seulement une notification.

Un motif textuel capture la forme habituelle d’une commande, pas toutes ses formes : celui-ci arrête rm -rf et rm -fr, mais rm -r -f avec des drapeaux séparés passe. C’est pourquoi chaque règle a besoin de tests, et pourquoi les règles de permission et le sandbox existent à côté des hooks.

Si vous préférez une sortie structurée, un hook peut se terminer avec le code 0 et afficher un JSON avec hookSpecificOutput, où hookEventName vaut PreToolUse et permissionDecision vaut allow, deny, ask ou defer, avec un permissionDecisionReason.

Le matcher PreToolUse, sans surprises

Le matcher filtre sur le nom de l’outil :

  • Bash, ou une liste comme Edit|Write — noms exacts.
  • Toute valeur avec d’autres caractères est une expression régulière, non ancrée : ^Notebook, mcp__memory__.*.
  • Les outils MCP sont nommés mcp__<server>__<tool>. Un matcher valant mcp__memory seul ne correspond à rien ; il faut mcp__memory__.*.
  • *, une chaîne vide, ou l’absence de matcher signifie tous les outils.

Il existe aussi un champ if qui utilise la syntaxe des règles de permission, comme Bash(git *). La documentation le qualifie de best-effort : pour un allow ou un deny ferme, utilisez le système de permissions.

Testez chaque hook : un appel bloqué, un appel autorisé

Un hook jamais testé est un hook dont vous espérez qu’il fonctionne. Le test le moins coûteux lui envoie le même JSON que Claude Code enverrait et vérifie le code de sortie :

echo '{"tool_name":"Bash","tool_input":{"command":"rm -rf build"}}' | python3 .claude/hooks/block_rm.py; echo "exit $?"   # expect 2
echo '{"tool_name":"Bash","tool_input":{"command":"ls build"}}'    | python3 .claude/hooks/block_rm.py; echo "exit $?"   # expect 0

Notre règle pour chaque hook : une action qui doit être bloquée et une qui doit passer, toutes deux dans le fichier de test. Le second test attrape l’autre panne fréquente : un motif si large qu’il bloque le travail normal.

Pourquoi une erreur de hook PreToolUse n’arrête rien

Les causes habituelles, toutes issues de la référence :

  1. Code de sortie 1 au lieu de 2. Une notification d’erreur s’affiche et l’outil s’exécute quand même.
  2. Timeout. Un hook de commande PreToolUse qui expire ne bloque pas ; l’appel continue par le flux normal de permissions. Les hooks de commande ont par défaut 600 secondes.
  3. Un matcher qui ne correspond jamais, comme mcp__memory sans .*.
  4. Un script en échec. Par défaut, un hook en échec ne bloque pas. Les versions récentes prennent en charge "onFailure": "block" (v2.1.295 ou ultérieure) pour bloquer quand un hook de commande échoue, expire ou renvoie une sortie invalide.

Hooks, règles de permission et skills : qui fait quoi

  • Les règles de permission (allow, ask, deny dans settings.json) sont vérifiées dans cet ordre : deny, puis ask, puis allow. Une règle deny s’applique même quand un hook PreToolUse renvoie allow.
  • Un hook bloquant (code 2) arrête l’appel avant l’évaluation des règles de permission, il l’emporte donc aussi sur une règle allow.
  • Les skills sont des instructions et des ressources que l’agent charge au besoin. Ils guident ; ils n’imposent rien.

Pour les dossiers confidentiels, commencez par des règles deny comme Read(./secrets/**). Connaissez leur limite : elles couvrent les outils de fichiers intégrés et des commandes comme cat, head et tail, mais pas un grep -r lancé depuis le dossier, ni un script qui ouvre lui-même des fichiers. Un hook PreToolUse peut inspecter le texte complet de la commande dans ces cas-là, et pour un contrôle au niveau du système d’exploitation, la documentation renvoie au sandbox.

Quand confier cela à quelqu’un

Si votre installation d’agents a grandi vite et que vous ne savez plus quelles règles tiennent vraiment, nous cartographions les agents, écrivons les règles de hook avec leurs tests et montrons l’exécution des tests, pas seulement la configuration. Vous pouvez voir comment notre propre système est construit sur la page réalisations.

Questions

Pourquoi mon hook PreToolUse ne bloque-t-il rien ?

Le plus souvent, il se termine avec le code 1. Seul le code de sortie 2 bloque l'appel d'outil ; tout autre code non nul est une erreur non bloquante et l'action est exécutée. Un hook qui expire ne bloque pas non plus.

Ai-je besoin de hooks si j'ai déjà des règles deny de permissions ?

Pour des règles simples, les règles deny passent en premier : elles sont vérifiées même quand un hook renvoie allow. Les hooks servent pour les logiques qu'un motif ne peut pas exprimer, comme inspecter le texte complet d'une commande avant son exécution.

Les règles deny sur Read empêchent-elles cat et grep dans Bash ?

Elles couvrent les outils de fichiers intégrés et les commandes de fichiers que Claude Code reconnaît dans Bash, comme cat, head et tail. Elles ne couvrent pas une commande qui lit des fichiers sans les nommer, comme un grep -r lancé depuis le dossier, ou un script qui ouvre lui-même des fichiers. Pour cela, la documentation renvoie au sandbox.

Où sont stockés les hooks Claude Code ?

Dans les fichiers de paramètres : ~/.claude/settings.json pour tous vos projets, .claude/settings.json pour un projet et votre équipe, ou .claude/settings.local.json pour vous seul. Les entrées de plusieurs fichiers sont fusionnées.

Contact

Une seule personne lit cette boîte — la même qui construit ce que vous commandez.

Décrivez votre besoin

Dites-nous ce que vous avez et le résultat attendu — même une seule petite tâche. Pas de cahier des charges : sous 48 heures, un plan et un prix par écrit, une question, ou un « ce n’est pas pour nous » honnête.

Ajoutez la situation, les fichiers ou liens, le délai, la langue, les limites de confidentialité et le budget si vous en avez un.

Fichier (facultatif)
Aucun fichier choisi

Un brief, une photo, un tableau — ce qui explique la tâche plus vite qu'un texte. Jusqu'à 8 Mo, stocké chez notre hébergeur. Merci de ne pas envoyer de documents personnels sensibles ou médicaux via le site.

L’envoi ne crée ni commande ni paiement. Réponse sous 48 heures. Plutôt par e-mail ? contact@castel.studio

Parlez-nous

Plutôt par téléphone ou messagerie ? Écrivez ou appelez — c'est le fondateur du studio qui répond.

Se connecter sur LinkedIn

Téléphone · WhatsApp · Telegram

France+33 7 49 57 41 69WhatsAppTelegram

Ukraine+38 063 95 77 111WhatsAppTelegram

Vous préférez une plateforme ?

Commandez le même travail sur une plateforme, avec son paiement sécurisé.

Studio d'IA à une personne, basé à Nice. Celui qui répond à votre message est celui qui fait le travail — pas d'intermédiaire commercial. Les fichiers sont traités sur notre propre machine, pas dans le cloud d'un tiers.

Nice 06100, France · Ouvrir dans les cartes →

SIREN 103 115 705