Le hook Stop de Claude Code exécute une commande chaque fois que l'agent principal Claude a fini de répondre : c'est donc l'endroit idéal pour envoyer une notification « terminé », logger le résultat ou lancer des vérifications. Il ne se déclenche pas quand tu interromps Claude, et un tour qui se termine sur une erreur d'API déclenche StopFailure à la place. Le hook reçoit le dernier message de Claude dans last_assistant_message, et il peut renvoyer decision: "block" avec une reason pour que Claude continue au lieu de s'arrêter.
01Quand Stop se déclenche, et quand il ne se déclenche pas
Stop se déclenche à la fin de chaque tour, pas seulement quand une tâche entière est terminée. Claude Code n'a aucune notion de « la tâche » ; il sait juste que Claude a arrêté de répondre et t'attend. Une petite question donne un Stop, et un refactoring de deux heures aussi.
Il ne se déclenche pas :
- quand tu appuies sur Échap ou que tu interromps le tour d'une autre manière ;
- quand le tour se termine à cause d'une erreur d'API, comme une limite de débit, une surcharge ou un échec d'authentification. Dans ces cas, Claude Code déclenche
StopFailure, avec un champerrorsur lequel tu peux filtrer (rate_limit,overloaded,server_error, etc.) ; - quand un subagent termine. Ça, c'est
SubagentStop.
Stop ne prend pas en charge les matchers : tout matcher que tu ajoutes est ignoré et le hook s'exécute à chaque tour.
02Le Stop hook le plus simple
Ajoute ceci à ~/.claude/settings.json pour lancer une commande chaque fois que Claude termine. Celui-ci joue un son sur macOS :
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "afplay /System/Library/Sounds/Glass.aiff"
}
]
}
]
}
}
Pour les commandes de notification desktop sur Mac, Windows et Linux, consulte comment recevoir une notification quand Claude Code a terminé. La suite de ce guide porte sur ce que tu peux faire avec l'entrée et la sortie du hook.
03Ce que reçoit le hook
En plus des champs communs (session_id, transcript_path, cwd, permission_mode, hook_event_name), un hook Stop reçoit :
last_assistant_messageLe texte de la dernière réponse de Claude pour ce tourstop_hook_activetrue si Claude continue déjà parce qu'un Stop hook l'a bloquébackground_tasksLes tâches encore en cours, comme des shells en arrière-plan ou des subagentssession_cronsLes réveils programmés dans cette session, par exemple via /loopUtilise last_assistant_message plutôt que de lire le fichier de transcript. Le transcript est écrit de façon asynchrone et peut ne pas encore contenir le dernier message quand le hook s'exécute.
background_tasks et session_crons t'indiquent si la session est vraiment terminée ou seulement en pause jusqu'à ce qu'une tâche en arrière-plan la relance. Un script de notification peut vérifier que les deux sont vides avant d'annoncer « terminé ».
04Mettre le dernier message de Claude dans la notification
Ce script (macOS et Linux, nécessite jq) affiche le début de la réponse de Claude et reste silencieux si des tâches tournent encore en arrière-plan :
#!/bin/bash
# ~/.claude/hooks/notify-done.sh
input=$(cat)
# Skip if background tasks are still in flight
busy=$(jq '(.background_tasks // []) | length' <<<"$input")
[ "$busy" -gt 0 ] && exit 0
msg=$(jq -r '(.last_assistant_message // "Claude has finished")
| gsub("\\s+"; " ") | .[0:140]' <<<"$input")
if command -v osascript >/dev/null 2>&1; then
osascript -e 'on run argv' \
-e 'display notification (item 1 of argv) with title "Claude has finished"' \
-e 'end run' "$msg"
elif command -v notify-send >/dev/null 2>&1; then
notify-send "Claude has finished" "$msg"
fi
exit 0
Enregistre-le, lance chmod +x ~/.claude/hooks/notify-done.sh, puis fais pointer le hook dessus :
{
"hooks": {
"Stop": [
{
"hooks": [{ "type": "command", "command": "~/.claude/hooks/notify-done.sh" }]
}
]
}
}
Le script sort toujours avec 0. C'est important : sur Stop, le code de sortie 2 veut dire « ne t'arrête pas », donc un script de notification qui sortirait par erreur avec 2 ferait continuer Claude.
05Faire continuer Claude
Stop fait partie des événements qui peuvent bloquer. Bloquer un arrêt signifie que Claude poursuit le tour, en prenant ta raison comme prochaine instruction. Tu peux le faire de deux façons :
- sortir avec
2: Claude reçoit ton stderr comme raison ; ou - sortir avec
0et afficher du JSON :
{
"decision": "block",
"reason": "The test suite is failing. Run npm test, fix the failures, then finish."
}
Si le hook donne des consignes plutôt que de signaler une erreur, hookSpecificOutput.additionalContext permet aussi de poursuivre la conversation, mais le transcript l'affiche comme un retour du Stop hook et non comme une erreur de hook :
{
"hookSpecificOutput": {
"hookEventName": "Stop",
"additionalContext": "Please run the test suite before finishing"
}
}
Éviter une boucle infinie
Un Stop hook qui bloque systématiquement ne laisserait jamais Claude s'arrêter. Deux choses te protègent :
stop_hook_activevauttruedès que Claude continue déjà à cause d'un Stop hook. Vérifie-le et sors tout de suite.- Claude Code termine quand même le tour une fois que les Stop hooks ont bloqué huit fois d'affilée sans appel d'outil entre-temps. La variable d'environnement
CLAUDE_CODE_STOP_HOOK_BLOCK_CAPpermet d'augmenter cette limite.
Voici un hook qui fait corriger les tests en échec à Claude une fois, puis le laisse s'arrêter :
#!/bin/bash
# ~/.claude/hooks/tests-must-pass.sh
input=$(cat)
[ "$(jq -r '.stop_hook_active' <<<"$input")" = "true" ] && exit 0
if ! npm test --silent >/dev/null 2>&1; then
jq -nc '{decision: "block", reason: "npm test is failing. Fix the failures, then finish."}'
fi
exit 0
Les hooks s'exécutent dans le répertoire de travail de la session, donc npm test tourne dans ton projet. Garde tes vérifications rapides : par défaut, un hook de type commande peut tourner jusqu'à 600 secondes, et Claude l'attend.
Si tu veux juste que Claude continue à avancer vers un objectif sans écrire de script, la commande intégrée /goal de Claude Code configure pour toi un Stop hook limité à la session.
06Des notifications pour les échecs aussi
Comme les erreurs d'API sautent Stop, une alerte « terminé » ne te dira pas que Claude a abandonné à cause d'une limite de débit. Ajoute le même script sous StopFailure si tu veux être prévenu. Sa sortie et son code de retour sont ignorés, il ne peut donc que notifier ou journaliser. Pour StopFailure, last_assistant_message contient le texte de l'erreur, par exemple "API Error: Rate limit reached".
07Où se situe Claude Code Notifier
Un Stop hook qui appelle un service de push est une très bonne façon de savoir que Claude a terminé pendant ton absence. Si tu veux le faire toi-même, le guide ntfy est un bon point de départ.
Claude Code Notifier utilise le même hook Stop, à une différence près : il peut renvoyer quelque chose. Son outil en ligne de commande gratuit, ccn, tourne sur Mac, Windows et Linux et affiche tout seul une notification sur le bureau. Avec l'app iPhone ou Android associée, tu reçois un push « Claude a terminé » dès qu'un tour se termine, et tu peux taper l'instruction suivante sur ton téléphone. ccn la renvoie depuis le hook Stop comme prochaine instruction pour Claude, et la session continue sans que tu sois au clavier. Si tu as tapé quelque chose dans les 30 dernières secondes, rien n'attend le téléphone. C'est un outil indépendant, non développé par Anthropic. Consulte s'éloigner pendant les tâches longues pour ce workflow et la page ccn pour l'installer.
08FAQ
Le Stop hook s'exécute-t-il quand Claude termine toute la tâche, ou après chaque réponse ?
Après chaque réponse. Stop se déclenche dès que l'agent principal a fini de répondre, quelle que soit la taille de la demande. Regarde background_tasks et session_crons dans l'entrée si tu dois savoir s'il reste du travail planifié.
Pourquoi mon Stop hook ne s'est-il pas déclenché ?
Tu as peut-être interrompu le tour, ce qui saute Stop, ou il s'est terminé sur une erreur d'API, qui déclenche StopFailure à la place. Sinon, vérifie que /hooks liste bien ton hook et que le script est exécutable. La checklist de dépannage couvre le reste.
Pourquoi Claude continue-t-il à travailler après l'exécution de mon Stop hook ?
Ton hook bloque l'arrêt, soit en sortant avec le code 2, soit en affichant decision: "block". Assure-toi qu'un hook de notification sort toujours avec 0, et que tout hook bloquant vérifie d'abord stop_hook_active.
Comment récupérer la réponse finale de Claude dans le hook ?
Lis last_assistant_message dans le JSON reçu sur stdin, par exemple avec jq -r '.last_assistant_message'. Ne compte pas sur transcript_path : le fichier peut ne pas encore contenir le dernier message quand Stop se déclenche.
Quelle est la différence entre Stop et SubagentStop ?
Stop concerne l'agent principal. SubagentStop se déclenche quand un sous-agent termine, filtre sur le type d'agent et accepte les mêmes champs de blocage. Une alerte « Claude a terminé » a sa place sur Stop.