Accueil / Étendre : compétences, MCP, sous-agents, hooks, plugins
Les événements de hook, du déclenchement au message
PreToolUse s'exécute avant l'outil et peut empêcher l'appel ; PostToolUse s'exécute après et ne peut que réagir, y compris en remplaçant le résultat de l'outil. Un hook communique sa décision par un objet JSON aux champs précis et par son code de sortie, jamais par une phrase écrite sur la sortie standard.
Un hook PreToolUse s'exécute avant que l'outil ne s'active : sa décision peut empêcher l'appel de se produire. Un hook PostToolUse s'exécute après, une fois l'outil déjà lancé : sa décision ne peut plus rien empêcher, seulement réagir, y compris en remplaçant le résultat de l'outil par le champ updatedToolOutput.
Une trentaine d'événements, du démarrage à la fin de session
La documentation officielle liste une trentaine d'événements du cycle de vie, de SessionStart et UserPromptSubmit jusqu'à PreCompact, WorktreeCreate ou SessionEnd. Elle prolonge la leçon précédente : un automatisme se déclare toujours sur l'un de ces événements, jamais sur une intention générale.
Le délai d'expiration dépend du type de hook, pas seulement de l'événement
Un hook command, http ou mcp_tool dispose par défaut de six cents secondes, trois événements l'abaissent à trente secondes et MessageDisplay à dix. Un hook prompt garde trente secondes, un hook agent soixante, quel que soit l'événement.
Ce qui atteint réellement le modèle : des champs JSON, pas du texte libre
Un hook ne communique pas sa décision en écrivant une phrase sur la sortie standard. Il renvoie un objet JSON dont le champ hookSpecificOutput.permissionDecision porte la valeur allow ou deny, accompagné d'un permissionDecisionReason qui explique le choix, et le champ hookEventName reprend le nom exact de l'événement déclencheur.
{
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "deny",
"permissionDecisionReason": "Commande destructive detectee dans le motif teste"
}
}
Le code de sortie du script compte aussi, indépendamment du JSON : un code de sortie 2 bloque l'action même si le corps JSON affirmait allow. Cette règle souffre d'exceptions nommées, PermissionRequest, StopFailure hors d'une séquence terminale, PermissionDenied, et surtout PostToolUse et PostToolUseFailure, où le code 2 devient non bloquant et se contente d'être affiché à Claude via l'erreur standard : logique, puisque l'outil a déjà fini de s'exécuter quand ce hook se déclenche.
Déférer plutôt que décider
Le champ permissionDecision accepte quatre valeurs : allow saute l'invite de permission, sauf exceptions documentées, deny empêche l'appel, ask fait confirmer par l'utilisateur, et defer termine le tour pour reprendre l'outil plus tard, en mode non interactif avec -p seulement. Pour renvoyer la décision au flux de permission habituel sans la forcer, un hook n'écrit aucune de ces valeurs : il sort avec le code 0 sans rapporter de décision, et l'appel continue son chemin normal d'approbation, comme si ce hook précis n'avait rien eu à en dire.
Tester un hook par son entrée standard
Une erreur de hook laisse passer l'action et ne s'annonce que par un avis dans la transcription, facile à manquer : un test mal formé rend un silence qui ressemble à « le hook ne réagit pas ». Le hook lit un JSON sur son entrée standard, donc vous pouvez le jouer à la main, avec un cas qu'il doit refuser et un contre-témoin qu'il doit laisser passer. Le script suivant et ses deux appels fabriquent leurs données :
cat > garde.sh << 'EOF'
#!/bin/sh
ENTREE=$(cat)
case "$ENTREE" in
*"npm publish"*) echo "Refusé : lancez npm publish --dry-run d'abord." >&2; exit 2 ;;
esac
exit 0
EOF
printf '%s' '{"tool_name":"Bash","tool_input":{"command":"npm publish"}}' | sh garde.sh; echo "code=$?"
printf '%s' '{"tool_name":"Bash","tool_input":{"command":"npm test"}}' | sh garde.sh; echo "code=$?"
Refusé : lancez npm publish --dry-run d'abord.
code=2
code=0
Le premier appel prouve que le garde refuse, le second que son silence signifie « autorisé » et non « cassé ». Écrivez le JSON avec printf '%s' entre apostrophes : un shell qui réduit une barre oblique inverse casse le JSON sans erreur visible.
Un garde en panne laisse-t-il passer ?
Depuis octobre 2026, un hook PreToolUse ou PermissionRequest dont la correspondance échoue, ou dont l'entrée ne se sérialise pas, bloque l'appel au lieu d'être ignoré. Sous Windows, un garde sans shell déclaré bloque aussi quand Git for Windows manque. Un hook lancé par PowerShell qui sortait avec le code 2 ne bloquait pas avant le correctif d'octobre : relisez les gardes écrits plus tôt. Pour qu'un garde de sécurité bloque dans tous les cas d'échec, déclarez "onFailure": "block".
Délai d'expiration selon le type de hook et l'événement
| Délai d'expiration par défaut | Type de hook concerné | Délai par défaut | Particularité |
|---|---|---|---|
| Hook command, http ou mcp_tool ordinaire | command, http, mcp_tool | 600 secondes | S'applique à la majorité des événements du cycle de vie |
| UserPromptSubmit, PreModelSwitch, PostModelSwitch | command, http, mcp_tool | 30 secondes | Ces trois événements abaissent le délai normalement fixé à 600 secondes |
| MessageDisplay | command, http, mcp_tool | 10 secondes | Le délai le plus court de tous les événements documentés |
| SessionEnd | Tous types confondus | 1,5 seconde pour l'ensemble des hooks de l'événement | Extensible jusqu'à 60 secondes si un hook déclare explicitement un délai plus long |
| Hook de type prompt | prompt | 30 secondes | Délai propre au type, indépendant de l'événement déclencheur |
| Hook de type agent | agent | 60 secondes | Délai propre au type, indépendant de l'événement déclencheur |
De l'événement à la décision transmise au modèle
Un développeur configure un hook PostToolUse qui renvoie un code de sortie 2 après chaque appel de l'outil Bash. Il lance ensuite une commande Bash depuis Claude Code et observe que la commande s'exécute jusqu'au bout.
Écrivez en une phrase ce que cette situation établit, et en une phrase ce qu'elle n'établit pas.
Ce que cela établit : Cette observation établit que, pour l'événement PostToolUse, un code de sortie 2 ne bloque pas l'action et se traite seulement comme un message affiché à Claude via l'erreur standard.
Ce que cela n’établit pas : Elle n'établit pas que le code de sortie 2 se comporte de la même façon sur tous les événements de hook, puisque PreToolUse honore ce même code en bloquant l'appel avant qu'il ne se produise.
Les trois calibrages faux les plus courants
- Trop large Cette observation prouve que le code de sortie 2 ne bloque jamais rien, quel que soit l'événement de hook concerné.
- Trop étroit Cette observation ne prouve rien sur le comportement du hook, puisqu'un seul appel de l'outil Bash a été testé.
- À côté Cette observation montre que le hook a mis plus de temps à s'exécuter que la commande Bash elle-même.
- PreToolUse peut empêcher un appel d'outil de se produire, PostToolUse ne peut rien empêcher mais peut remplacer le résultat une fois l'outil lancé.
- Un hook se teste à la main en lui envoyant un JSON sur son entrée standard, avec un cas à refuser et un contre-témoin à laisser passer.
- La décision d'un hook passe par le champ hookSpecificOutput.permissionDecision, avec les valeurs allow, deny, ask ou defer, jamais par une phrase sur la sortie standard.
- Un code de sortie 2 bloque l'action même quand le JSON affirme allow, sauf sur des événements nommés où il devient un simple message affiché à Claude.
- Pour déférer au flux de permission habituel, un hook sort avec le code 0 sans rapporter de décision, et un garde de sécurité déclare onFailure: "block" pour bloquer aussi en cas de panne.
Écrivez un hook PreToolUse minimal qui sort avec le code 0 sans rien écrire sur sa sortie standard, déclarez le sur un événement anodin, et confirmez que l'appel continue son chemin normal d'approbation plutôt que d'être forcé.
Chaque affirmation datable de cette leçon renvoie ici au texte public qui la porte. Une source qui ne s’ouvre pas ne prouve rien.
- Claude Code, hooks : updatedToolOutput, onFailure, Windows sans Git Bash consultée le 2026-10-24
- Journal de Claude Code : gardes de hook qui bloquent en cas de panne, PowerShell et code 2 consultée le 2026-10-24