on(eventName, handler).
Construisez votre premier mod avant de commencer ici. Pour chaque événement et ses champs exacts, consultez la référence ou lisez les types pour votre build.
Comment un hook gère un événement
Un hook se situe entre un événement et ce que Claude Code ferait à ce sujet, il peut donc observer l’événement, le réécrire, ou y répondre lui-même. Il reçoit trois arguments : l’API mods en tant que$, l’événement en tant que e, et le gestionnaire suivant en tant que next. Les gestionnaires d’un événement forment une chaîne middleware. next(e) appelle le gestionnaire suivant, qui est le hook d’un autre mod ou, à la fin de la chaîne, le comportement propre de Claude Code, et il se résout au résultat. Ce que votre hook fait avec next décide lequel des trois il fait.
Observer un événement
Pour observer un événement sans le modifier, faites votre travail et retourneznext(e). Ce hook enregistre chaque outil que Claude s’apprête à utiliser :
● my-mod: Claude is about to use Bash apparaît dans la transcription, où my-mod est le nom de votre plugin. L’outil s’exécute comme il le ferait sans le mod.
Pour agir après l’événement, await next(e), faites votre travail, et retournez le résultat. Ce hook enregistre chaque outil après son exécution :
next(e) s’est résolu à.
Réécrire un événement
Pour modifier ce sur quoi Claude Code agit, comme le texte d’une invite, appeleznext avec une copie modifiée de l’événement. L’événement lui-même est immuable : il est gelé à chaque profondeur, et l’assignation à un champ lève une exception. Ce hook supprime les espaces de chaque invite avant qu’elle ne soit envoyée :
await next(e), puis retourner une copie du résultat avec un champ remplacé.
Répondre à un événement
Pour gérer un événement vous-même, retournez un résultat sans appelernext. Cela court-circuite la chaîne, donc les mods ultérieurs et le comportement propre de Claude Code ne s’exécutent pas. Ce hook refuse chaque commande Bash :
deny comme le résultat de l’outil. Chaque événement a sa propre forme de résultat, que la référence des événements énumère.
Filtrer les événements qu’un hook gère
Pour exécuter un hook pour certains événements seulement, passez un filtre comme deuxième argument àon. Claude Code appelle le filtre un matcher. C’est un objet dont les champs sont comparés avec ceux de l’événement, et le hook s’exécute seulement quand chaque champ correspond. Un champ peut être une valeur, un tableau de valeurs autorisées, ou une expression régulière.
Chaque ligne dans cet exemple enregistre la même fonction, hook, pour un ensemble plus restreint d’appels d’outils :
hook s’exécute une fois pour un appel Bash, Edit ou Write, et une fois pour un appel à un outil dont le nom commence par mcp__github__. Un appel à n’importe quel autre outil, comme Read, ne correspond à aucun des trois, donc hook ne s’exécute pas pour lui.
Le nom de l’événement peut être un wildcard. 'classic.*' correspond à chaque événement hook des paramètres. '*' correspond à chaque événement sauf les événements de télémétrie, que vous hookez par nom ou en tant que 'telemetry.*'.
Enregistrez chaque événement une fois par matcher. Si vous appelez on deux fois pour session.start sans matcher, le module échoue à charger avec on("session.start") is registered twice without a matcher. Mettez tout ce que votre mod fait au démarrage de la session dans un hook.
Hook ce que Claude fait
Hookez ces événements pour voir ou modifier un appel d’outil, une invite, ou un tour au moment où cela se produit. Pour chaque événement et ce qu’un hook peut retourner, consultez la référence des événements.Garder ou modifier un appel d’outil
Un hooktool.call voit chaque outil que Claude s’apprête à utiliser, il peut donc refuser l’appel, modifier ses arguments, ou le laisser passer. tool.call se déclenche quand Claude Code s’apprête à exécuter un outil, y compris les appels qu’un sous-agent fait et les appels aux outils MCP. e.tool est le nom de l’outil et les arguments de l’outil sont des champs de e, comme e.command pour Bash. Quand vous appelez next(e), Claude Code exécute la vérification des permissions puis l’outil.
Ce hook refuse une commande Bash qui force-push, et dit à Claude pourquoi :
git push --force, la commande ne s’exécute pas et aucune invite de permission n’apparaît, car le hook n’appelle jamais next. Claude lit le texte deny comme le résultat de l’outil, donc écrivez-le comme une instruction sur laquelle Claude peut agir. Chaque autre commande Bash s’exécute comme elle le ferait sans le mod.
Pour agir après l’exécution d’un outil, await next(e), faites votre travail, et retournez ce que next vous a donné. Ce hook enregistre chaque fichier .mdx que Claude modifie, avec $.ui.log, qui ajoute une ligne atténuée à la transcription que Claude ne lit pas :
.mdx, une ligne atténuée dans la transcription nomme le fichier. Rien n’est enregistré pour un autre type de fichier, ou pour un appel qui a été refusé ou échoué. La vue de Claude de l’appel ne change pas, car le hook retourne le résultat qu’il a reçu.
Pour modifier un appel, passez des arguments modifiés à next. Pour réessayer un appel, appelez next(e) à nouveau : un hook qui voit isError sur le premier résultat peut exécuter l’outil une deuxième fois et retourner ce résultat. Pour répondre à un appel vous-même, retournez un objet avec un champ result, comme { result: 'Skipped by my-mod' }, sans appeler next. Quand vous faites cela, aucune invite de permission n’apparaît et l’outil ne s’exécute pas, donc le résultat que vous retournez est tout ce que Claude apprend sur ce qui s’est passé.
Les hooks dans les paramètres gérés de votre organisation s’exécutent avant le hook tool.call de n’importe quel mod, et un bloc de l’un d’eux est final.
Tenir un appel d’outil jusqu’à ce que l’utilisateur décide
Un hook peut mettre en pause un appel d’outil et demander à l’utilisateur quoi faire avant qu’il ne continue. Un hooktool.call peut await avant d’appeler next ou de retourner, et l’appel d’outil reste en attente jusqu’à ce moment. Pour poser la question à l’utilisateur, appelez $.ui.ask. Il affiche votre question au-dessus d’une liste numérotée de vos options, dans la boîte de dialogue que Claude utilise pour vous poser une question, et se résout à l’étiquette que l’utilisateur choisit. Après vos options, la boîte de dialogue ajoute une ligne pour taper une réponse différente et une ligne Chat about this.
Le motif RISKY dans cet exemple correspond à rm -r, rm -rf, git reset --hard, et git push avec --force, et il manque d’autres orthographes comme git push -f. Ce module demande avant d’exécuter une commande Bash qui correspond au motif :
rm -rf build, la question apparaît avec la commande dedans, et la commande attend la réponse :
- L’utilisateur choisit Run it : le hook appelle
next(e), et la vérification des permissions habituelle s’exécute toujours après - L’utilisateur choisit Refuse : la commande ne s’exécute pas, et Claude lit le texte
deny - L’utilisateur tape une réponse :
$.ui.askse résout au texte tapé. Le hook le compare avecRun it, donc n’importe quel autre texte refuse la commande. - Personne ne répond :
$.ui.askrejette quand l’utilisateur rejette la question ou choisit Chat about this, et dans une exécutionclaude -p, donc le bloccatchlaisse la réponse àRefuse
$.ui.ask, car ce temps ne compte pas contre la limite de temps de 10 secondes du hook. Le temps passé à attendre une promesse de votre propre compte. Claude Code saute un hook qui expire, donc la commande tenue s’exécuterait.
Réécrire ou ajouter à une invite
Un hookprompt.submit voit chaque invite avant le début du tour, il peut donc réécrire le texte ou l’ajouter. e.text est ce qui a été tapé.
Ce hook ajoute le nom de la branche actuelle pour Claude chaque fois qu’une invite mentionne une pull request :
open a PR for this change, votre message ressemble au même dans la transcription, et Claude lit aussi une ligne comme Current branch: feature/auth après. Une invite qui ne mentionne pas une pull request passe inchangée, et git ne s’exécute pas.
D’autres événements couvrent le reste de ce que Claude lit : prompt.section pour chaque section de l’invite système, prompt.context pour le contexte envoyé avec le premier message, et skill.prompt pour le texte d’une compétence. Le texte de ces hooks qui change entre les requêtes invalide le cache d’invite.
Suivre un tour
Un tour est tout ce que Claude fait en réponse à une invite. Hookezturn.start, turn.step, et turn.complete pour en suivre un :
Écrivez un hook
turn.step comme un générateur asynchrone, car l’événement diffuse. yield* next(e) transfère la réponse au fur et à mesure qu’elle diffuse et s’évalue au résultat terminé. Ce hook enregistre combien de chaque requête l’API Claude a servi à partir du cache d’invite :
result.usage contient les quatre comptes de jetons que l’API Claude rapporte pour une requête, plus le model qui a répondu : input_tokens, output_tokens, cache_read_input_tokens, et cache_creation_input_tokens. Le hook s’exécute aussi pour les requêtes des sous-agents, donc vérifiez e.agentId quand vous voulez seulement la conversation principale.
Hook les événements hook des paramètres
Les hooks des paramètres sont les hooks de commande, HTTP, d’invite et d’agent que vous configurez dans les fichiers de paramètres. Chaque événement hook des paramètres, commeStop, SessionEnd, ou PostToolUse, est aussi un événement nommé classic. suivi du nom de l’événement hook des paramètres, comme classic.Stop. e est le JSON qu’un hook des paramètres reçoit sur stdin, y compris transcript_path.
Ce hook utilise Stop, qui se déclenche quand Claude finit de répondre, pour enregistrer où la transcription de la session est sauvegardée :
next(e), il observe donc l’événement et ne change rien à la façon dont le tour se termine.
Exécuter aux côtés d’autres mods
Plusieurs mods peuvent hooker le même événement, et n’importe lequel d’eux peut échouer. Si votre mod bloque les appels d’outils, vérifiez sa position dans la chaîne et ce qui se passe quand son hook échoue.L’ordre dans lequel les mods s’exécutent
Les hooks sur le même événement forment une chaîne middleware. Chaquenext d’un mod appelle le hook du mod suivant, et le dernier next atteint le comportement propre de Claude Code. Le premier mod est le plus externe : il voit l’événement avant les autres et le résultat après eux, et il décide si les autres s’exécutent du tout. Un mod ultérieur ne peut pas empêcher un mod antérieur de voir un événement.
Claude Code ordonne la chaîne par où chaque mod vient :
- La garde intégrée
sec-default@builtin, un mod intégré à Claude Code que/pluginénumère en tant quecc-plugin-sec-default, où il charge, les mods que votre organisation énumère dansprependPlugins, puis tout autre mod qui compte comme celui de votre organisation et n’est pas dansappendPlugins - Les mods que vous installez
- Les mods que votre organisation énumère dans
appendPlugins - D’autres mods intégrés à Claude Code
dependencies dans son manifeste. Dans un module, les hooks s’exécutent dans l’ordre que register a appelé on.
Où les hooks des paramètres s’exécutent dans l’ordre
Les hooksPreToolUse configurés dans les fichiers de paramètres s’exécutent aussi pendant un appel d’outil, à des points fixes dans la chaîne des mods :
- Hooks
PreToolUsedes paramètres gérés : s’exécutent avant le hooktool.calldu premier mod, et un bloc de l’un d’eux est final, donc aucun mod ne voit l’appel. - Hooks
PreToolUsede chaque autre fichier de paramètres et deshooks/hooks.jsondes plugins : s’exécutent après que le dernier mod appellenext, comme partie du comportement propre de Claude Code. Un mod qui répond àtool.callsans appelernextles empêche de s’exécuter, et un mod qui appellenextvoit leur décision dans le résultat qu’il retourne.
tool.check est l’événement où Claude Code décide si un appel d’outil peut s’exécuter. Il se déclenche après ces hooks et les règles de permission ont décidé, et next(e) se résout à leur décision. Un hook sur tool.check peut retourner une décision différente, comme { decision: 'allow' }, il peut donc approuver un appel qu’un hook du deuxième groupe a bloqué. Étendre les permissions avec des hooks énumère quelles décisions tiennent sur un mod.
Gérer un hook qui échoue
Un hook qui échoue ne casse pas la session, et vous pouvez décider ce qui se passe à la place. Quand un hook sans gestionnaire.catch lève une exception, expire, ou retourne un résultat de la mauvaise forme, ce qui se passe ensuite dépend de s’il avait appelé next :
- Il a échoué avant d’appeler
next: Claude Code le saute, et le gestionnaire suivant s’exécute à sa place - Il a échoué après que
nextse soit résolu : ce résultat tient, et rien ne s’exécute une deuxième fois
my-mod: tool.call hook skipped: threw Error: boom. Où vous la lisez dépend de la session, comme Découvrir pourquoi un mod ne fait rien l’énumère. Un hook ui.render dont le dessin ne valide pas est rapporté différemment, comme Construire un arbre à partir d’éléments le décrit.
Pour faire échouer un hook qui bloque les appels fermé, ajoutez un gestionnaire d’erreur .catch qui répond à sa place. Ici, guard est votre fonction hook :
guard fonctionne, le gestionnaire ne s’exécute jamais. Quand guard lève une exception ou expire sur un appel Bash, Claude Code appelle le gestionnaire avec le même événement. Le gestionnaire retourne { deny }, donc la commande ne s’exécute pas, et Claude lit le texte avec throw ou timeout à la fin. Sans le gestionnaire, Claude Code sauterait guard et exécuterait la commande. Le gestionnaire a une seconde pour répondre.
Prochaines étapes
- Utiliser l’API mods : ajouter des commandes et des outils, appeler un modèle, et exécuter du travail sur un minuteur
- Dessiner dans l’interface : afficher ce que vos hooks collectent dans un volet ou au-dessus de l’invite
- Tester un mod : déclencher n’importe lequel de ces événements à partir d’un test
- Référence des mods : chaque événement, chaque méthode API mods, et les limites