Accueil / Étendre : compétences, MCP, sous-agents, hooks, plugins
Les événements de hook, du déclenchement au message
PreToolUse peut empêcher un appel de se produire, PostToolUse ne peut plus que réagir après coup, et la décision d'un hook passe par des champs JSON précis, 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. Cette différence de moment détermine ce qu'un hook peut raisonnablement faire à chaque événement, et elle explique pourquoi un même mécanisme de blocage ne se comporte pas de la même façon selon l'événement qui le porte.
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. Chacun couvre un moment précis : un fichier qui change, une bascule de modèle, un sous-agent qui démarre ou se termine, une invite en cours d'expansion. Un automatisme voulu, présenté dans la leçon précédente comme la traduction mécanique d'une règle qu'on espérait voir suivie, se déclare toujours sur l'un de ces événements précis, 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 de type command, http ou mcp_tool dispose par défaut de six cents secondes pour répondre. Trois événements abaissent ce délai à trente secondes, UserPromptSubmit, PreModelSwitch et PostModelSwitch, et un seul le réduit à dix secondes, MessageDisplay. Un hook de type prompt garde un délai fixe de trente secondes, un hook de type agent un délai fixe de soixante secondes, quel que soit l'événement qui le déclenche. SessionEnd fonctionne différemment : l'ensemble de ses hooks partage un budget d'une seconde et demie, extensible jusqu'à soixante secondes seulement si un hook déclare explicitement un délai plus long.
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. Pour ajouter de l'information sans rien bloquer, hookSpecificOutput.additionalContext insère du texte que le modèle lira comme s'il venait d'ailleurs dans la conversation.
{
"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 documente deux valeurs, allow et deny. Pour renvoyer la décision au flux de permission habituel plutôt que de la forcer, un hook n'écrit aucune troisième valeur dans ce champ : 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.
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 plus que réagir une fois l'outil déjà lancé.
- Le délai par défaut de six cents secondes pour un hook command, http ou mcp_tool descend à trente secondes sur trois événements et à dix secondes sur un seul.
- La décision d'un hook passe par le champ hookSpecificOutput.permissionDecision, avec les valeurs confirmées allow et deny, jamais par une phrase écrite sur la sortie standard.
- Un code de sortie 2 bloque l'action même quand le JSON affirme allow, sauf sur une liste d'événements nommés où il devient un simple message affiché à Claude.
- Pour déférer au flux de permission habituel plutôt que de forcer allow ou deny, un hook sort avec le code 0 sans rapporter de décision, il n'existe pas de troisième valeur écrite dans permissionDecision.
É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, valeurs de permissionDecision et code de sortie 2 selon l'événement consultée le 2026-09-02