Aller au contenu
Mastering Claude

Accueil / Étendre : compétences, MCP, sous-agents, hooks, plugins

Étendre : compétences, MCP, sous-agents, hooks, plugins11 minApplication

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".

Figure 1

Délai d'expiration selon le type de hook et l'événement

Délai d'expiration par défautType de hook concernéDélai par défautParticularité
Hook command, http ou mcp_tool ordinairecommand, http, mcp_tool600 secondesS'applique à la majorité des événements du cycle de vie
UserPromptSubmit, PreModelSwitch, PostModelSwitchcommand, http, mcp_tool30 secondesCes trois événements abaissent le délai normalement fixé à 600 secondes
MessageDisplaycommand, http, mcp_tool10 secondesLe délai le plus court de tous les événements documentés
SessionEndTous types confondus1,5 seconde pour l'ensemble des hooks de l'événementExtensible jusqu'à 60 secondes si un hook déclare explicitement un délai plus long
Hook de type promptprompt30 secondesDélai propre au type, indépendant de l'événement déclencheur
Hook de type agentagent60 secondesDélai propre au type, indépendant de l'événement déclencheur
Le tableau croise le type de hook avec son délai par défaut, et isole les événements qui abaissent ce délai ou qui suivent une règle à part comme SessionEnd.
Figure 2

De l'événement à la décision transmise au modèle

01
Événement déclenché
Un outil est sur le point de s'exécuter, ou vient de le faire, selon l'événement enregistré.
02
Hook exécuté
Le harnais lance la commande, l'appel HTTP, l'outil MCP, l'invite ou l'agent déclaré pour cet événement.
03
Décision rendue en JSON
hookSpecificOutput.permissionDecision porte allow ou deny, accompagné de permissionDecisionReason, et hookEventName reprend le nom de l'événement.
04
Contexte ajouté ou action bloquée
additionalContext enrichit ce que le modèle voit, ou l'appel est annulé avant de se produire si l'événement est PreToolUse.
05
Code de sortie 2 en filet
Il bloque l'action même si le JSON dit allow, sauf pour les événements qui ne l'honorent pas, comme PostToolUse où il n'est qu'affiché à Claude via stderr.
La séquence situe le moment où PreToolUse peut encore empêcher l'appel, et celui où PostToolUse ne peut plus que constater ce qui vient de se produire.
Calibrez vous-même

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 qu’il faut retenir
  • 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.
À faire maintenant

É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é.

Vérifier à la source

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.