
Le rituel ruff + pytest repose sur votre vigilance : vous le lancez à la main, et un oubli casse la boucle. Les hooks Claude Code déclenchent des commandes shell autour des événements de la session (PreToolUse, PostToolUse, Stop, etc.). Vous pouvez exécuter ruff automatiquement après chaque Edit, bloquer rm -rf avant qu'il ne parte, ou notifier qu'une édition a laissé des erreurs de lint, sans dépendre de votre attention.
Ce que vous allez apprendre
Section intitulée « Ce que vous allez apprendre »- Configurer un hook
PostToolUsequi lanceruffaprès chaqueEdit/Write - Configurer un hook
PreToolUsequi bloque une commande destructive - Choisir le bon événement et le bon matcher
- Lire les codes de sortie pour informer, bloquer ou ignorer
Dans quel contexte utiliser cette fonctionnalité ?
Section intitulée « Dans quel contexte utiliser cette fonctionnalité ? »Le point commun de ces quatre situations est le passage du conseil à la
contrainte. Une consigne écrite dans un CLAUDE.md est du contexte : elle
est lue, pondérée, parfois ignorée. Un hook est un processus lancé par l'outil
lui-même, avec le même résultat à chaque exécution, y compris quand la session
est longue et que le contexte s'est dilué.
- Vous voulez un rituel déterministe, pas seulement conseillé
- Vous partagez un projet avec une équipe et voulez garantir que le lint tourne
- Vous voulez interdire systématiquement une commande (pas juste refuser en live)
- Vous voulez notifier un événement (ex : un test cassé) sans bloquer
Prérequis
Section intitulée « Prérequis ».claude/settings.jsondéjà en place (voir settings.json avancé)ruffetpytestinstallés et fonctionnels surlab-claude- À l'aise avec la syntaxe des permissions (voir settings.json avancé)
Les événements utiles au quotidien
Section intitulée « Les événements utiles au quotidien »La doc officielle liste beaucoup d'événements. Pour un démarrage pragmatique, concentrez-vous sur ceux-ci :
| Événement | Se déclenche | Exemple d'usage |
|---|---|---|
PreToolUse | Avant l'exécution d'un outil, peut bloquer | Interdire rm -rf, bloquer les curl non whitelistés |
PostToolUse | Après exécution réussie d'un outil | Lancer ruff check après Edit ou Write |
PostToolUseFailure | Après échec d'un outil | Loguer ou notifier un échec |
Stop | Quand Claude termine son tour | Lancer pytest -q en fin de session |
SessionStart | Au démarrage d'une session | Afficher l'état git courant |
Structure minimale dans settings.json
Section intitulée « Structure minimale dans settings.json »Les hooks vivent sous la clé hooks du settings.json. Chaque événement contient une liste d'entrées {matcher, hooks} : le premier niveau filtre, le second exécute. Cette imbrication surprend au début, mais elle permet d'attacher plusieurs commandes au même filtre et plusieurs filtres au même événement.
{ "hooks": { "PostToolUse": [ { "matcher": "Edit|Write", "hooks": [ { "type": "command", "command": "uv run ruff check .", "timeout": 30 } ] } ] }}Explication rapide :
matcher: filtre sur le nom de l'outil (Edit,Write,Bash, etc.).Edit|Write= l'un ou l'autretype: "command": exécuter une commande shellcommand: la commande à lancertimeout: optionnel, en secondes. Sans lui, une commandecommanddispose de 600 secondes, ce qui est très long pour un hook déclenché à chaque édition : fixez une valeur basse pour qu'une commande bloquée se manifeste vite
Application sur lab-claude
Section intitulée « Application sur lab-claude »Objectif : deux hooks utiles immédiatement, lint automatique après édition et blocage systématique des commandes destructives.
Hook 1 : ruff après chaque Edit ou Write
Section intitulée « Hook 1 : ruff après chaque Edit ou Write »Ce premier hook s'exécute après que l'outil a réussi, donc il n'empêche
rien : il rapporte. La sortie de ruff revient dans la conversation, ce qui
donne au modèle l'information nécessaire pour corriger de lui-même dans la
foulée. C'est le cas d'usage le plus rentable pour démarrer, parce qu'il ne
risque pas de bloquer votre session si la configuration est imparfaite.
-
Éditez
.claude/settings.jsonet ajoutez la sectionhooks{"$schema": "https://json.schemastore.org/claude-code-settings.json","permissions": {"allow": ["Bash(uv run ruff check:*)", "Bash(uv run pytest:*)"],"deny": ["Bash(rm -rf:*)", "Read(./.env)"]},"hooks": {"PostToolUse": [{"matcher": "Edit|Write","hooks": [{"type": "command","command": "uv run ruff check .","timeout": 30}]}]}} -
Validez la syntaxe JSON
Fenêtre de terminal python3 -c "import json; json.load(open('.claude/settings.json'))" && echo "JSON valide" -
Testez dans une session
Faites-lui modifier un fichier Python. Après l'
Edit,ruff check .tourne automatiquement, et le résultat revient dans la conversation.
Hook 2 : bloquer rm -rf avant exécution
Section intitulée « Hook 2 : bloquer rm -rf avant exécution »Un deny dans permissions couvre déjà rm -rf, mais un hook PreToolUse permet d'ajouter un message explicite et de centraliser la politique :
{ "hooks": { "PreToolUse": [ { "matcher": "Bash", "hooks": [ { "type": "command", "command": "bash -c 'input=$(cat); echo \"$input\" | grep -qE \"rm -rf|rm -fr\" && { echo \"rm -rf bloque par hook\" >&2; exit 2; } || exit 0'" } ] } ] }}Ce qui se passe :
- Le hook reçoit l'entrée de l'outil sur
stdin(JSON) - S'il détecte
rm -rf, il sort avecexit 2et un message surstderr - Exit code 2 = Claude voit ça comme un blocage et ne lance pas la commande
Une limite à connaître : ce filtre travaille sur le JSON brut, pas sur la
commande analysée. Il déclenche donc aussi si la chaîne rm -rf apparaît
ailleurs dans la charge utile, et il se laisse contourner par une écriture
équivalente (rm --recursive --force, un alias, une variable). Traitez-le comme
un garde-fou contre l'accident, pas comme une barrière contre un contournement
délibéré : la vraie interdiction reste permissions.deny.
Les codes de sortie à connaître
Section intitulée « Les codes de sortie à connaître »Le code de sortie est le seul canal par lequel votre script pilote la
session : il n'y a pas de configuration « bloquant / non bloquant » dans le
settings.json. Retenez surtout la distinction entre 2 et le reste, car
c'est elle qui décide si Claude reçoit votre message ou si personne ne le lit.
Retenez aussi que le flux compte autant que le code : stderr remonte à
Claude sur un blocage, stdout sert à l'affichage.
| Exit code | Effet |
|---|---|
0 | Succès, le tour continue normalement |
2 | Blocage explicite, stderr est renvoyé à Claude comme feedback |
| autre | Erreur non bloquante, signalée dans la session |
Règle simple :
- Vous voulez informer sans bloquer →
exit 0et écrire surstdout - Vous voulez bloquer avec un message →
exit 2et écrire surstderr
Matchers : filtrer finement
Section intitulée « Matchers : filtrer finement »Le matcher décide sur quel outil votre hook se déclenche, et sa syntaxe
change selon les caractères employés. Une valeur composée uniquement de lettres,
chiffres et barres verticales est comparée littéralement ; dès qu'un autre
caractère apparaît, la valeur devient une expression régulière non ancrée.
La conséquence piège tout le monde une fois : Edit.* déclenche aussi sur
NotebookEdit, il faut écrire ^Edit$ pour viser l'outil seul.
| Matcher | Déclenche sur |
|---|---|
"*" ou omis | Tous les outils |
"Edit" | Uniquement l'outil Edit |
"Edit|Write" | Edit ou Write |
"Bash" | Toutes les commandes shell |
"^Notebook" | Regex : tous les outils commençant par Notebook |
Combinaison avec settings.json et skills
Section intitulée « Combinaison avec settings.json et skills »Quatre mécanismes se recouvrent partiellement, et les confondre produit des configurations qui ne s'appliquent jamais. Le critère de choix tient en une question : voulez-vous interdire, déclencher, enchaîner ou conseiller ? Une interdiction se pose dans les permissions, où elle est évaluée avant toute exécution ; un déclenchement automatique est un hook ; le reste relève du contexte fourni au modèle.
| Besoin | Bon endroit |
|---|---|
| Interdire complètement une commande | permissions.deny dans settings.json |
| Déclencher automatiquement autour d'un événement | Hook |
| Enchaîner une procédure invoquée à la main | Skill |
| Conseiller une convention de code | Rule ou CLAUDE.md |
Les hooks sont le seul mécanisme déterministe : CLAUDE.md, rules et skills sont du contexte, les hooks sont du code exécuté.
Erreurs fréquentes
Section intitulée « Erreurs fréquentes »Ces cinq symptômes se diagnostiquent tous de la même façon : élargir puis
resserrer. Commencez par un matcher à "*" et une commande qui écrit
simplement une trace ; si le hook se déclenche, le problème vient du filtre, pas
de la configuration. La dernière ligne est la plus sournoise, car un hook mal
quoté sort en 0 et laisse croire que la protection fonctionne.
| Symptôme | Cause probable | Correction |
|---|---|---|
| Le hook ne se déclenche pas | Mauvais event ou matcher trop strict | Tester avec "matcher": "*" puis resserrer |
timeout atteint | Commande trop lente (ex : pytest complet après chaque Edit) | Déclencher pytest sur Stop plutôt que sur chaque édition |
| Blocage non compris par Claude | Exit code autre que 2 | Utiliser exit 2 pour bloquer explicitement |
| Hook exécuté trop souvent | Matcher trop large | Préciser "Edit|Write" plutôt que "*" |
rm -rf passe quand même | Hook shell mal quoté | Vérifier que la commande utilise bien stdin et un grep correct |
Checklist de fin de guide
Section intitulée « Checklist de fin de guide »Reprenez ces cinq points sur votre propre settings.json avant de considérer le
sujet clos. Les deux derniers portent sur des choix de conception plutôt que sur
la syntaxe : ce sont eux qui déterminent si vos hooks resteront utilisables dans
six mois ou finiront désactivés parce qu'ils ralentissent chaque interaction.
- J'ai au moins un hook
PostToolUsefonctionnel surEdit|Write - J'ai validé la syntaxe JSON de
settings.json - J'ai testé un hook bloquant avec
exit 2et un message - Je sais où mettre un hook (automatisme) vs un skill (procédure manuelle)
- Les hooks de validation lourde sont sur
Stop, pas sur chaqueEdit
À retenir
Section intitulée « À retenir »- Les hooks sont le seul garde-fou déterministe de Claude Code
PostToolUse+ matcherEdit|Write= rituel lint automatiquePreToolUse+exit 2= blocage avec message- Gardez les hooks rapides, sinon vous fatiguez la session
CLAUDE.mdconseille,settings.jsonautorise, les hooks exécutent
Pour aller plus loin
Section intitulée « Pour aller plus loin »- Agent Skills : le standard portable : Le format ouvert derrière les skills, et ce qui les distingue d'un hook déclenché par événement.
- Skills avancées : scripts et ressources : La logique déportée dans un vrai script, réutilisable par un hook comme par une skill.
- Mode headless et intégration CI : Vos garde-fous locaux rejoués en CI, là où aucune confirmation interactive n'est possible.