Hooks utiles
Sur cette page
- 1. La vérification de types après chaque modification
- 2. Les tests en arrière-plan
- 3. Recontextualiser après une compaction
- 4. Les variables d’environnement de la session
- 5. La revue de doublons par un agent
- 6. Juger plutôt que tester : le hook de type prompt
- Les principes derrière ces exemples
- À vous de jouer
- Points clés à retenir
Les hooks corrigent des faiblesses courantes du développement assisté, qui se manifestent surtout sur les gros projets, là où plus personne n’a l’ensemble du code en tête : le changement incomplet, la régression découverte trop tard, le contexte perdu à la compaction, la fonctionnalité écrite en double. Cette leçon rassemble six hooks éprouvés, du plus simple au plus ambitieux, chacun illustrant un principe transposable à vos propres besoins.
1. La vérification de types après chaque modification
Quand Claude modifie la signature d’une fonction, il ne met pas toujours à jour tous les endroits où elle est appelée : il change la définition dans schema.ts et manque l’appel dans main.ts, un fichier qu’il n’avait aucune raison d’ouvrir. L’erreur ne se verra qu’au prochain build, plusieurs tâches plus tard, quand le contexte se sera évaporé. Le remède est un compilateur branché au bon moment du cycle :
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"if": "Edit(*.ts)",
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/typecheck.sh",
"args": [],
"timeout": 120
}
]
}
]
}
}
#!/bin/bash
# .claude/hooks/typecheck.sh : relaie les erreurs de type à Claude
OUTPUT=$(npx tsc --noEmit 2>&1)
if [ $? -ne 0 ]; then
echo "Erreurs de type après ta modification :" >&2
echo "$OUTPUT" | head -40 >&2
exit 2
fi
exit 0
Sur PostToolUse, le code 2 ne bloque rien, l’outil a tourné, mais il montre stderr à Claude : les erreurs lui arrivent dans la foulée de sa modification, et il corrige les autres fichiers sans que vous interveniez. Le compilateur tient le rôle du collègue qui connaît tout le projet par cœur. La condition if évite de lancer tsc quand Claude touche un fichier Markdown. Le principe vaut pour tout langage typé (mypy, cargo check, go vet) et, pour les autres, pour une suite de tests.
2. Les tests en arrière-plan
Une suite de tests complète peut prendre des minutes ; la faire attendre à chaque écriture paralyse la session. Un hook async tourne en arrière-plan pendant que Claude continue, et son résultat lui est livré au tour suivant :
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"if": "Edit(src/**)",
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/run-tests-async.sh",
"args": [],
"async": true
}
]
}
]
}
}
#!/bin/bash
# .claude/hooks/run-tests-async.sh : tests en arrière-plan, résultat au tour suivant
RESULT=$(npm test 2>&1 | tail -20)
jq -n --arg r "$RESULT" '{"hookSpecificOutput":{"hookEventName":"PostToolUse","additionalContext":("Résultat des tests :\n" + $r)}}'
Un hook asynchrone ne peut rien bloquer, par construction : l’action est passée depuis longtemps. Il sert à informer. jq -n --arg construit le JSON en échappant correctement la sortie des tests.
3. Recontextualiser après une compaction
Quand la fenêtre se remplit, la compaction résume la conversation et peut perdre des détails. Un hook SessionStart avec le matcher compact réinjecte ce qui compte, puisque le texte qu’il écrit sur stdout est ajouté au contexte de Claude :
{
"hooks": {
"SessionStart": [
{
"matcher": "compact",
"hooks": [
{
"type": "command",
"command": "echo 'Rappel : pnpm, pas npm. Lance pnpm test avant de commiter. Sprint en cours : refonte auth.' && git log --oneline -5"
}
]
}
]
}
}
Pour un rappel à chaque session, CLAUDE.md est plus simple ; le hook prend le relais pour une sortie dynamique, comme les derniers commits.
4. Les variables d’environnement de la session
L’outil Bash ne conserve pas les export d’une commande à l’autre. Un hook SessionStart peut écrire des lignes export dans le fichier désigné par CLAUDE_ENV_FILE, que Claude Code source avant chaque commande :
#!/bin/bash
if [ -n "$CLAUDE_ENV_FILE" ]; then
echo 'export NODE_ENV=test' >> "$CLAUDE_ENV_FILE"
echo 'export PATH="$PATH:./node_modules/.bin"' >> "$CLAUDE_ENV_FILE"
fi
exit 0
Avec direnv, la commande direnv export bash > "$CLAUDE_ENV_FILE" dans un hook SessionStart et un hook CwdChanged recharge l’environnement à chaque changement de dossier.
5. La revue de doublons par un agent
Dans un gros projet, Claude crée parfois une requête de base de données qui existe déjà sous un autre nom : il ne peut réutiliser que ce qu’il a vu, et getPendingOrders() enfouie dans un fichier qu’il n’a pas ouvert n’existe pas de son point de vue. Le code fonctionnera, personne ne s’en apercevra en revue, et le projet comptera deux définitions de la même chose. La parade consiste à faire relire Claude par un second agent, qui arrive sans le contexte de la conversation et va réellement explorer le dossier :
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"if": "Edit(src/queries/**)",
"hooks": [
{
"type": "agent",
"prompt": "Un fichier de src/queries/ vient d'être modifié : $ARGUMENTS. Cherche dans src/queries/ une fonction existante qui fait déjà la même chose que le code ajouté. S'il y a un doublon, réponds {\"ok\": false, \"reason\": \"<nom de la fonction existante et ce qu'il faut réutiliser>\"}, sinon {\"ok\": true}.",
"timeout": 120
}
]
}
]
}
}
Un hook de type agent lance un sous-agent avec des outils (lecture, recherche), qui répond ok: true ou ok: false avec une raison ; sur PostToolUse, la raison est renvoyée à Claude, qui supprime le doublon et réutilise l’existant. Les hooks agent sont expérimentaux ; pour une version plus stable, un hook command peut lancer claude -p avec la même consigne et renvoyer sa réponse dans additionalContext. Dans les deux cas, chaque modification surveillée coûte un appel de modèle : réservez ce hook aux dossiers où la duplication fait vraiment mal, d’où le if.
6. Juger plutôt que tester : le hook de type prompt
Pour une décision qui demande du jugement et non une règle déterministe, un hook de type prompt envoie les données de l’événement à un modèle, en une passe, sans outils :
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "prompt",
"prompt": "Claude veut s'arrêter : $ARGUMENTS. Vérifie que toutes les tâches demandées sont terminées. Sinon, réponds {\"ok\": false, \"reason\": \"ce qui reste à faire\"}."
}
]
}
]
}
}
Si le modèle répond ok: false, Claude continue avec la raison comme consigne suivante. Un hook prompt suffit quand les données de l’événement permettent de trancher ; un hook agent s’impose quand il faut vérifier l’état réel du code, par exemple lancer la suite de tests avant d’autoriser l’arrêt.
Les principes derrière ces exemples
Trois idées reviennent. Branchez un outil qui existe déjà (compilateur, linter, tests) au bon moment du cycle : c’est gratuit et déterministe. Informez Claude plutôt que de l’interrompre quand l’action est faite : stderr avec le code 2 sur PostToolUse, additionalContext sur un hook asynchrone. Payez du modèle seulement là où il y a du jugement, avec les hooks prompt et agent, en les limitant par if et timeout. La page officielle Automate actions with hooks donne d’autres recettes : notification de bureau, protection de fichiers, auto-approbation du passage hors mode plan, audit des changements de configuration.
À vous de jouer
- Installez le hook de vérification de types (ou son équivalent dans votre langage). Demandez à Claude d’ajouter un paramètre à une fonction appelée ailleurs et observez-le corriger les sites d’appel.
- Installez le hook de tests asynchrone et vérifiez que le résultat arrive au tour suivant, sans bloquer.
- Écrivez un hook
SessionStartavec le matchercompact, forcez une compaction avec/compact, puis demandez à Claude ce qu’il sait du sprint en cours. - Mesurez : avec
/cost, comparez une session avec et sans le hookagentsursrc/queries/.
Sources officielles : Automate actions with hooks · Hooks reference · Tools reference
Points clés à retenir
- Un
PostToolUsequi lancetsc --noEmitet sort avec 2 relaie les erreurs de type à Claude pendant qu’il a encore la modification en tête async: truefait tourner les tests en arrière-plan ; le résultat arrive au tour suivant viaadditionalContextSessionStartavec le matchercompactréinjecte le contexte perdu ;CLAUDE_ENV_FILEpersiste des variables pour Bash- Les hooks
promptetagentapportent du jugement, au prix d’appels de modèle : limitez-les avecifettimeout - Branchez les outils existants, informez plutôt qu’interrompre, payez du modèle seulement là où il faut juger