Stop Hook Claude Code : vérifier avant « terminé »
Ajoutez un contrôle local qui bloque la fin si un test échoue ou si un fichier manque.
Sommaire
Stop Hook Claude Code : vérifier avant « terminé »
Le message « terminé » de Claude Code signifie seulement que l’agent veut conclure son tour. Il ne prouve ni que les tests ont été exécutés, ni que l’artefact de build est à jour. Un Stop Hook lance un contrôle local court à ce moment-là et peut empêcher la fin lorsqu’un constat précis échoue. Il ne remplace pas la CI, la suite de tests complète ni une validation humaine.
Stop Hook, CLAUDE.md et CI : trois rôles distincts
- Un Stop Hook exécute un contrôle bref à la fin de la réponse :
git diff --check, un test ciblé ou la présence d’un fichier attendu. - CLAUDE.md indique à l’agent les règles et les commandes à respecter, mais ce fichier n’exécute aucune commande.
- La CI s’exécute indépendamment après un push ou une pull request. Elle reste le garde-fou de l’équipe.
N’ajoutez pas à ce hook un déploiement, une publication ou une écriture dans un service externe. Déclenchés à chaque fin de tour, ces effets de bord deviennent difficiles à reproduire et à annuler.
Définir un résultat vérifiable
Avant d’écrire le hook, formulez un petit contrat :
- Affirmation : ce que l’agent peut annoncer après son tour, par exemple « le build a été produit ».
- Preuve : la commande ou le fichier qui l’établit, comme
npm test -- --runInBandettest -s dist/app.js. - Succès : les deux contrôles retournent le code
0. - Blocage : le hook renvoie un JSON avec
decision: "block"et unreasonbref.
Exécutez d’abord ces commandes sans hook. Si elles prennent plusieurs minutes ou exigent le réseau, remplacez-les par un contrôle local plus ciblé ; le parcours exhaustif reste du ressort de la CI.
Si Claude Code utilise un fournisseur d’API, ouvrez d’abord la documentation BetterToken à jour, configurez votre propre API Key dans l’outil et envoyez une courte requête de test. Vérifiez ensuite dans Dashboard le modèle, le statut et l’usage des tokens attendus. Le hook local n’a pas besoin de la clé ; ne l’inscrivez jamais dans ses journaux.
Configurer un Stop Hook minimal
Ajoutez un hook de projet dans .claude/settings.json avec un délai limité :
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "./scripts/check-before-stop.sh",
"timeout": 30
}
]
}
]
}
}
Enregistrez ensuite ce script sous scripts/check-before-stop.sh. Les commandes ci-dessous sont des exemples pour un projet Node ; adaptez-les aux commandes et aux chemins réellement présents dans votre dépôt.
#!/usr/bin/env sh
set -eu
if [ -t 0 ]; then
input='{"stop_hook_active":false}'
else
input=$(cat)
fi
stop_hook_active=$(printf '%s' "$input" | node -e '
let raw = "";
process.stdin.on("data", chunk => raw += chunk);
process.stdin.on("end", () => {
try { process.stdout.write(String(Boolean(JSON.parse(raw).stop_hook_active))); }
catch { process.stdout.write("false"); }
});')
block_stop() {
if [ "$stop_hook_active" = "true" ]; then
printf '%s\n' "Le contrôle Stop échoue toujours : $block_reason. Exécutez-le manuellement ; la CI reste obligatoire." >&2
exit 0
fi
BLOCK_REASON="$block_reason" node -e 'process.stdout.write(JSON.stringify({decision:"block",reason:`Échec du contrôle Stop : ${process.env.BLOCK_REASON}`}) + "\n")'
exit 0
}
if ! npm test -- --runInBand >/dev/null 2>&1; then
block_reason='exécuter npm test et corriger le test en échec'
block_stop
fi
if ! test -s dist/app.js; then
block_reason='reconstruire dist/app.js'
block_stop
fi
printf '%s\n' 'Contrôle Stop réussi : tests et dist/app.js'
exit 0
Rendez le fichier exécutable :
chmod +x scripts/check-before-stop.sh
D’après la documentation actuelle de Claude Code, un Stop Hook peut renvoyer un JSON structuré avec le code 0 : decision: "block" empêche la fin du tour et reason en transmet le motif. Les codes non nuls et les timeouts ont leur propre sémantique d’erreur de hook ; n’en faites pas l’unique contrat de blocage. Le contrôle doit tenir dans le délai configuré.
Lors de la tentative de fin suivante, stop_hook_active vaut true si Claude Code continue déjà à cause d’un Stop Hook. La branche fail-open écrit alors un bref avertissement dans stderr et renvoie 0, au lieu de rebloquer aveuglément le même échec. Elle évite une boucle sans supprimer la barrière de CI. Si vous devez bloquer de nouveau, définissez votre propre compteur et une limite explicite, sans supposer un nombre fixe non documenté de tentatives.
Vérifier le cycle complet à la main
Ne vous contentez pas de vérifier que le script se lance dans le terminal ; testez chaque branche :
- Dans le script, remplacez provisoirement
dist/app.jspardist/missing.js, puis exécutezprintf '%s\n' '{"stop_hook_active":false}' | ./scripts/check-before-stop.sh; echo $?. Attendez un JSON avec"decision":"block", unreasonbref et le code0. - Rétablissez le bon chemin, produisez le build et répétez la commande. Le résultat attendu est le code
0. - Indiquez de nouveau un fichier absent, demandez à Claude Code une petite modification réversible et vérifiez que
decision: "block"maintient la conversation ouverte avec le motif du contrôle. - Sans corriger le chemin, exécutez
printf '%s\n' '{"stop_hook_active":true}' | ./scripts/check-before-stop.sh; echo $?. Le script doit écrire un bref avertissement dans stderr et renvoyer0: vous avez alors validé la branche anti-boucle. - Rétablissez le chemin ou créez un artefact actuel. À la fin suivante, le hook doit renvoyer
0et autoriser le tour à se terminer.
Ce test distingue un Stop Hook opérationnel d’un script qui échoue dans le terminal tout en laissant Claude Code s’arrêter.
Traiter un Stop bloqué et reprendre proprement
Séparez deux cas. Si le hook renvoie un JSON avec decision: "block", lisez reason : la condition contrôlée a échoué normalement. Lancez ce contrôle, corrigez le test ou le code, vérifiez que le fichier vient de la commande actuelle, puis reprenez la tâche Claude Code.
Si la commande du hook se termine avec un code non nul ou un timeout, il s’agit d’une erreur d’exécution du hook, pas d’un blocage confirmé par reason. Lisez l’erreur et stderr, lancez le script à la main et corrigez son chemin, ses droits, sa dépendance ou son délai avant de retester.
Journalisez seulement le nom du contrôle et son résultat. N’affichez ni API Key, ni contenu de .env, ni prompt complet, ni journal de test intégral. Un git diff --check réussi ne valide pas la logique métier ; l’existence d’un fichier ne garantit pas que le build soit récent. Le hook ne contrôle que les affirmations que vous avez explicitement codées.
Garder le workflow API séparé
Avec un fournisseur d’API, créez et gérez la clé dans votre propre compte. Le Stop Hook reste local : il n’a pas besoin d’accéder à la clé, aux prompts complets ou aux journaux du Dashboard. En cas d’incident d’API, consultez la documentation actuelle pour la Base URL et la configuration ; ne mélangez pas ce diagnostic avec le contrôle local de fin de tour.
Sources
- Claude Code Hooks Reference — consultée le 22 août 2026
- BetterToken : configurer Claude Code