- Bloquer les opérations dangereuses avant leur exécution, comme les commandes shell destructrices ou l’accès non autorisé aux fichiers
- Journaliser et auditer les appels d’outils pour la conformité, le débogage ou l’analyse
- Transformer les entrées et les sorties pour nettoyer les données, injecter des identifiants ou rediriger les chemins de fichiers
- Exiger une approbation humaine pour les actions sensibles comme les écritures de base de données ou les appels API
- Suivre le cycle de vie de la session pour gérer l’état, nettoyer les ressources ou envoyer des notifications
Fonctionnement des hooks
1
Un événement se déclenche
Quelque chose se produit lors de l’exécution de l’agent et le SDK déclenche un événement : un outil est sur le point d’être appelé (
PreToolUse), un outil a renvoyé un résultat (PostToolUse), un sous-agent a démarré ou s’est arrêté, l’agent est inactif ou l’exécution s’est terminée. Consultez la liste complète des événements.2
Le SDK collecte les hooks enregistrés
Le SDK vérifie les hooks enregistrés pour ce type d’événement. Cela inclut les hooks de rappel que vous transmettez dans
options.hooks et les hooks de commande shell à partir des fichiers de paramètres lorsque l’entrée settingSources ou setting_sources correspondante est activée, ce qui est le cas pour les options query() par défaut.3
Les matchers filtrent les hooks qui s'exécutent
Si un hook a un motif
matcher (comme "Write|Edit"), le SDK le teste par rapport à la cible de l’événement (par exemple, le nom de l’outil). Les hooks sans matcher s’exécutent pour chaque événement de ce type.4
Les fonctions de rappel s'exécutent
Chaque fonction de rappel du hook correspondant reçoit des informations sur ce qui se passe : le nom de l’outil, ses arguments, l’ID de session et d’autres détails spécifiques à l’événement.
5
Votre rappel retourne une décision
Après avoir effectué toute opération (journalisation, appels API, validation), votre rappel retourne un objet de sortie qui indique à l’agent quoi faire : autoriser l’opération, la bloquer, modifier l’entrée ou injecter du contexte dans la conversation.
PreToolUse (étape 1) avec un matcher "Write|Edit" (étape 3) afin que le rappel se déclenche pour les outils Write et Edit. Lorsqu’il est déclenché, le rappel reçoit l’entrée de l’outil (étape 4), vérifie si le chemin du fichier cible un fichier .env et retourne permissionDecision: "deny" pour bloquer l’opération (étape 5) :
file_path se termine par /.env, quelle que soit la casse. Sous Windows, file_path utilise des barres obliques inverses, comme C:\project\.env, et ce hook autorise un appel Write ou Edit vers C:\project\.env.
Hooks disponibles
Le SDK fournit des hooks pour différentes étapes de l’exécution de l’agent. Certains hooks sont disponibles dans les deux SDK, tandis que d’autres sont réservés à TypeScript.Configurer les hooks
Pour configurer un hook, transmettez-le dans le champhooks de vos options d’agent (ClaudeAgentOptions en Python, l’objet options en TypeScript). Cet extrait suppose que vous avez déjà défini un rappel hook, comme protect_env_files en Python ou protectEnvFiles en TypeScript à partir de l’exemple ci-dessus :
hooks est un dictionnaire en Python ou un objet en TypeScript, où :
- Les clés : les noms d’événements hook tels que
'PreToolUse','PostToolUse'et'Stop' - Les valeurs : des tableaux de matchers, chacun contenant un motif de filtre optionnel et vos fonctions de rappel
Matchers
Utilisez les matchers pour filtrer quand vos rappels se déclenchent. Le champmatcher correspond à une valeur différente selon le type d’événement hook. Par exemple, les hooks basés sur les outils correspondent au nom de l’outil, tandis que les hooks Notification correspondent au type de notification.
Les matchers du SDK suivent les mêmes règles que les matchers dans les fichiers de paramètres. Cette section documente les chemins d’évaluation de chaîne exacte et d’expression régulière, leurs exigences de version, et les valeurs de matcher pour chaque type d’événement.
Utilisez le motif
matcher pour cibler des outils spécifiques chaque fois que possible. Un matcher avec 'Bash' s’exécute uniquement pour les commandes Bash, tandis que l’omission du motif exécute vos rappels pour chaque occurrence de l’événement.
Fonctions de rappel
Entrées
Chaque rappel hook reçoit trois arguments :- Données d’entrée : un objet typé contenant les détails de l’événement. Chaque type de hook a sa propre forme d’entrée. Par exemple,
PreToolUseHookInputincluttool_nameettool_input, tandis queNotificationHookInputinclutmessage. Consultez les définitions de type complètes dans les références du SDK TypeScript et Python.- Toutes les entrées de hook partagent
session_id,cwdethook_event_name. agent_idetagent_typesont remplis lorsque le hook se déclenche à l’intérieur d’un sous-agent. En TypeScript, ceux-ci se trouvent sur l’entrée de hook de base et sont disponibles pour tous les types de hook. En Python, ils sont des champs optionnels surPreToolUse,PostToolUse,PostToolUseFailureetPermissionRequest, et des champs requis surSubagentStartetSubagentStop.
- Toutes les entrées de hook partagent
- ID d’utilisation d’outil (
str | None/string | undefined) : met en corrélation les événementsPreToolUseetPostToolUsepour le même appel d’outil. - Contexte : en TypeScript, contient une propriété
signal(AbortSignal) pour l’annulation. En Python, cet argument est réservé pour une utilisation future.
Sorties
Votre rappel retourne un objet avec deux catégories de champs :- Champs de niveau supérieur sont acceptés sur chaque événement :
systemMessageaffiche un message à l’utilisateur, etcontinue(continue_en Python) détermine si l’agent continue à s’exécuter après ce hook. Certains événements les rejettent ou les livrent ailleurs. La section de chaque événement sur la page des hooks indique où ils aboutissent. hookSpecificOutputcontrôle l’opération actuelle. Les champs que vous définissez à l’intérieur dépendent du type d’événement hook :- Pour les hooks
PreToolUse, c’est là que vous définissezpermissionDecision("allow","deny","ask"ou"defer"),permissionDecisionReasonetupdatedInput. Si vous retournez"defer", le tour se termine par un message de résultat dont lestop_reasonest"tool_deferred", afin que vous puissiez reprendre l’appel plus tard. - Pour les hooks
PostToolUse, vous pouvez définiradditionalContextpour ajouter des informations au résultat de l’outil. Pour remplacer la sortie de l’outil avant que Claude ne la voie, définissezupdatedToolOutput, qui fonctionne pour n’importe quel outil dans les deux SDK. Le champ plus ancienupdatedMCPToolOutputremplace uniquement la sortie des outils MCP. - Dans le SDK TypeScript, un rappel
PostToolUsepeut également retournerclassifierContext, une courte note sur le résultat de l’appel d’outil pour le classificateur de permission du mode auto. Parce que votre rappel s’exécute dans le propre processus de votre application, le classificateur peut peser une déclaration d’utilisateur que vous relayez dans la note comme intention de l’utilisateur. Le champ nécessite le SDK Agent TypeScript v0.3.236 ou ultérieur. Annoter un résultat pour le classificateur du mode auto couvre le plafond de longueur, la règle synchrone uniquement, et ce qu’il ne faut pas mettre dans la note.
- Pour les hooks
{} pour autoriser l’opération sans modifications. Les hooks de rappel du SDK utilisent le même format de sortie JSON que les hooks de commande shell Claude Code, qui documente chaque champ et option spécifique à l’événement. Pour les définitions de type du SDK, consultez les références du SDK TypeScript et Python.
Lorsque plusieurs hooks
PreToolUse retournent des décisions différentes, deny a priorité sur defer, qui a priorité sur ask, qui a priorité sur allow.Sortie asynchrone
Par défaut, l’agent attend que votre hook retourne avant de continuer. Si votre hook effectue un effet secondaire, tel que la journalisation ou l’envoi d’un webhook, et n’a pas besoin d’influencer le comportement de l’agent, vous pouvez retourner une sortie asynchrone à la place. Cela indique à l’agent de continuer immédiatement sans attendre la fin du hook. Dans cet extrait,send_to_logging_service en Python et sendToLoggingService en TypeScript représentent n’importe quelle fonction de journalisation que vous définissez :
Les sorties asynchrones ne peuvent pas bloquer, modifier ou injecter du contexte dans l’opération puisque l’agent a déjà avancé. Utilisez-les uniquement pour les effets secondaires comme la journalisation, les métriques ou les notifications.
Exemples
Plusieurs exemples dans cette section montrent uniquement la fonction de rappel. Pour en exécuter un, enregistrez le rappel sous l’événement correspondant dans le champhooks de vos options, comme indiqué dans Configurer les hooks.
Modifier l’entrée de l’outil
Cet exemple intercepte les appels d’outil Write et réécrit l’argumentfile_path pour y ajouter le préfixe /sandbox. Le rappel retourne updatedInput avec le chemin modifié et permissionDecision: 'allow' pour approuver automatiquement l’opération réécrite :
Associez
updatedInput à permissionDecision: 'allow' pour approuver automatiquement l’entrée modifiée, ou à permissionDecision: 'ask' pour la montrer à l’utilisateur. Si vous omettez permissionDecision, l’entrée modifiée s’applique toujours et passe par l’évaluation de permission normale. Avec 'defer', updatedInput est ignoré. Retournez toujours un nouvel objet plutôt que de muter le tool_input original../sandbox ou /tmp/sandbox (macOS ne permet pas de créer un répertoire /sandbox au niveau racine), puis demandez à l’agent d’écrire un fichier : le résultat de l’outil Write dans le flux de messages nomme le chemin avec votre préfixe sandbox plutôt que celui que Claude a demandé.
Ajouter du contexte et bloquer un outil
Cet exemple refuse les appels d’outil dont lefile_path commence par /etc et explique pourquoi au modèle et à l’utilisateur :
permissionDecision: 'deny'arrête l’appel d’outil.permissionDecisionReasonindique au modèle pourquoi.systemMessagemontre à l’utilisateur ce qui s’est passé.
PreToolUse avec un matcher Write|Edit et demandez à l’agent de créer un fichier sous /etc : le résultat de l’outil Write dans le flux de messages contient Writing to /etc is not allowed, et aucun fichier n’est créé.
Approuver automatiquement des outils spécifiques
Par défaut, l’agent peut demander une permission avant d’utiliser certains outils. Cet exemple approuve automatiquement les outils de système de fichiers en lecture seule (Read, Glob, Grep) en retournantpermissionDecision: 'allow', les laissant s’exécuter sans confirmation de l’utilisateur, à l’exception des lectures depuis des chemins réseau, tout en laissant tous les autres outils soumis aux vérifications de permission normales :
Enregistrer plusieurs hooks
Quand un événement se déclenche, les hooks correspondants s’exécutent en parallèle. Pour les décisions de permission, le résultat le plus restrictif s’applique. Ledeny d’un seul hook bloque l’appel d’outil. Parce que l’ordre de complétion est non-déterministe, écrivez chaque hook pour agir indépendamment plutôt que de compter sur l’exécution préalable d’un autre hook.
L’exemple ci-dessous enregistre trois vérifications indépendantes, chacune avec un matcher omis. Les noms de hook qu’il contient, tels que audit_logger en Python ou auditLogger en TypeScript, représentent des rappels que vous définissez :
Filtrer avec des matchers multi-outils
Utilisez des matchers multi-outils pour partager un rappel entre outils connexes. Cet exemple enregistre trois matchers avec des portées différentes, et chaque hook qu’il nomme représente un rappel que vous définissez :- Une liste exacte séparée par des barres (
Write|Edit|NotebookEdit) déclenchefile_security_hookpour les outils Write, Edit et NotebookEdit. - Une regex (
^mcp__) déclenchemcp_audit_hookpour tout outil MCP dont le nom commence parmcp__. - Un matcher omis déclenche
global_loggersans filtrer par nom d’outil.
Suivre l’activité des sous-agents
Utilisez les hooksSubagentStop pour surveiller quand les sous-agents terminent leur travail. Consultez le type d’entrée complet dans les références du SDK TypeScript et Python. Cet exemple enregistre un résumé chaque fois qu’un sous-agent se termine :
[SUBAGENT] Completed: avec l’ID du sous-agent et le chemin de sa transcription.
Effectuer des requêtes HTTP à partir des hooks
Les hooks peuvent effectuer des opérations asynchrones comme les requêtes HTTP. Capturez les erreurs à l’intérieur de votre hook au lieu de les laisser se propager. Cet exemple envoie un webhook après chaque exécution d’outil, enregistrant quel outil a été exécuté et quand. Le hook capture les erreurs afin qu’un webhook échoué n’interrompe pas l’agent :Transférer les notifications à Slack
Utilisez les hooksNotification pour recevoir les notifications système de l’agent et les transférer vers des services externes. Dans les sessions SDK, Claude Code exécute ce hook pour les types de notification suivants :
permission_promptune fois qu’une demande de permission a attendu environ six secondes sur votre rappelcanUseTool. Nécessite TypeScript Agent SDK v0.3.233 ou ultérieur, ou Python Agent SDK v0.2.139 ou ultérieurelicitation_completeetelicitation_responsepour les flux d’élicitation de saisie utilisateur
idle_prompt, auth_success, et elicitation_dialog, à partir de l’interface utilisateur interactive que les sessions SDK n’exécutent pas.
Chaque notification inclut un champ message avec une description lisible par l’homme et optionnellement un title.
Cet exemple transfère chaque notification à un canal Slack. Il nécessite une URL de webhook entrant Slack, que vous créez en ajoutant une application à votre espace de travail Slack et en activant les webhooks entrants :
Notification se déclenche, le hook publie le message de la notification, préfixé par Agent status:, sur le canal que votre webhook cible.
Corriger les problèmes courants
Hook ne se déclenche pas
- Vérifiez que le nom de l’événement hook est correct et sensible à la casse (
PreToolUse, paspreToolUse) - Vérifiez que votre motif matcher correspond exactement au nom de l’outil
- Assurez-vous que le hook se trouve sous le type d’événement correct dans
options.hooks - Pour les hooks non basés sur les outils qui prennent en charge les matchers, comme
NotificationetSubagentStop, les matchers correspondent à des champs différents, etStopignore complètement les matchers (consultez motifs matcher) - Les hooks peuvent ne pas se déclencher lorsque l’agent atteint la limite
max_turnscar la session se termine avant que les hooks puissent s’exécuter
Matcher ne filtre pas comme prévu
Les matchers ne correspondent qu’aux noms d’outils, pas aux chemins de fichiers ou à d’autres arguments. Pour filtrer par chemin de fichier, vérifieztool_input.file_path à l’intérieur de votre hook :
Délai d’expiration du hook
Claude Code exécute chaque rappel avec un délai d’expiration, que vous définissez en secondes avec le champtimeout sur son HookMatcher. Lorsque vous n’en définissez pas, Claude Code utilise la valeur par défaut de l’événement : 600 secondes pour la plupart des événements, 30 secondes pour UserPromptSubmit, PreModelSwitch et PostModelSwitch, et 10 secondes pour MessageDisplay. Claude Code exécute les rappels SessionEnd lors de l’arrêt sous le budget de délai d’expiration SessionEnd plus court, 1,5 secondes par défaut.
Lorsqu’un rappel dépasse son délai d’expiration, Claude Code l’annule et rejette sa sortie, et la session continue plutôt que de se bloquer. Ce qui se passe ensuite dépend de l’événement :
PreToolUse: Claude Code bloque l’appel d’outil. À sa place, Claude reçoit un résultat d’outil indiquant que le hook n’a pas répondu avant son délai d’expiration, et le tour continue. Avant la v2.1.210, Claude Code signalait le délai d’expiration à Claude comme un refus utilisateur, ce qui faisait arrêter les sessions sans surveillance et attendre une entrée.PostToolUseetPostToolUseFailure: Claude Code conserve le résultat de l’outil et le tour continue.UserPromptSubmitetUserPromptExpansion: Claude Code bloque le prompt avec un message nommant le hook et le délai d’expiration, et la session continue. Avant la v2.1.208, Claude Code terminait la requête avecerror_during_executionlorsqu’un rappel sur ces événements expirait.StopetSubagentStop: le rappel expiré compte comme ne retournant aucune décision. L’agent ou le sous-agent s’arrête comme si ce rappel l’avait autorisé, et une décision de vos autres hooks sur l’événement s’applique toujours. Avant Claude Code v2.1.273, un rappelStopouSubagentStopexpiré comptait comme une exécution de hook échouée, et Claude Code rejetait les décisions de vos autres hooks sur l’événement.SessionStart: le rappel expiré compte comme ne retournant aucune sortie, et la session continue avec la sortie de vos autres hooksSessionStart.PreModelSwitch: Claude Code bloque le changement de modèle. Un hook qui ne répond pas n’a pas approuvé le changement.- Autres événements, tels que
Notification,PreCompactetPostModelSwitch: Claude Code consigne l’échec dans les logs et continue.
Stop ou SessionStart expire dans la session principale, Claude Code ajoute également un SDKInformationalMessage au flux de messages indiquant que l’application pilotant la session n’a pas répondu. Les expirations ultérieures ne répètent pas ce message tant que votre application reste sans réponse.
Si vous interrompez la requête pendant qu’un rappel est en attente, Claude Code annule l’appel d’outil en attente. Avant la v2.1.208, l’appel d’outil pouvait toujours se poursuivre si vous interrompiez pendant un rappel PreToolUse en attente.
Si votre rappel a besoin de plus de temps, définissez un timeout plus élevé sur son HookMatcher. En TypeScript, utilisez le AbortSignal du troisième argument de rappel pour gérer l’annulation correctement lorsque le délai d’expiration se déclenche.
Outil bloqué de manière inattendue
- Vérifiez tous les hooks
PreToolUsepour les retourspermissionDecision: 'deny' - Ajoutez la journalisation à vos hooks pour voir quel
permissionDecisionReasonils retournent - Vérifiez que les motifs matcher ne sont pas trop larges : un matcher vide correspond à tous les outils
Entrée modifiée non appliquée
-
Assurez-vous que
updatedInputse trouve à l’intérieur dehookSpecificOutput, pas au niveau supérieur : -
N’associez pas
updatedInputavecpermissionDecision: 'defer', qui supprime l’entrée modifiée. OmettrepermissionDecisionest correct : l’entrée modifiée s’applique toujours par l’évaluation des permissions normale. Vous pouvez également retourner'allow'pour approuver automatiquement l’entrée modifiée ou'ask'pour la montrer à l’utilisateur pour approbation -
Incluez
hookEventNamedanshookSpecificOutputpour identifier le type de hook pour lequel la sortie est destinée
Hooks de session non disponibles en Python
SessionStart et SessionEnd peuvent être enregistrés en tant que hooks de rappel du SDK en TypeScript, mais ne sont pas disponibles dans le SDK Python car son type HookEvent les omet. En Python, ils ne sont disponibles que comme hooks de commande shell définis dans les fichiers de paramètres tels que .claude/settings.json. Les fichiers de paramètres que charge votre application SDK dépendent de setting_sources ou de settingSources. Si vous définissez cette option, incluez la source qui contient les hooks :
client.receive_response() comme déclencheur.
Les demandes de permission des sous-agents se multiplient
Lors du lancement de plusieurs sous-agents, chacun peut demander des permissions séparément pour ses propres appels d’outils. Pour éviter les demandes répétées, utilisez les hooksPreToolUse pour approuver automatiquement des outils spécifiques, ou configurez des règles de permission, que les sous-agents héritent de la conversation parent.
Boucles de hook récursives avec des sous-agents
Un hookUserPromptSubmit qui lance des sous-agents peut créer des boucles infinies si ces sous-agents déclenchent le même hook. Pour éviter cela :
- Utilisez une variable partagée ou un état de session pour suivre si vous êtes déjà à l’intérieur d’un sous-agent
- Limitez les hooks pour qu’ils s’exécutent uniquement pour la session d’agent de niveau supérieur
systemMessage n’apparaît pas dans la sortie
Le champsystemMessage affiche un message à l’utilisateur, pas au modèle. Sur Claude Code v2.1.227 ou ultérieur, le systemMessage d’un hook peut apparaître dans le flux de messages en tant que SDKInformationalMessage. Que ce soit le cas dépend de l’événement. La section de chaque événement sur la page des hooks indique comment la sortie apparaît. Pour transmettre du contexte au modèle à la place, retournez additionalContext.
Avant la v2.1.227, le SDK ne faisait apparaître la sortie du hook dans le flux de messages que pour les hooks SessionStart et Setup. Pour tout autre événement, la sortie n’apparaissait que dans les événements de cycle de vie que includeHookEvents (include_hook_events en Python) ajoute. L’entrée de cette option couvre les événements de cycle de vie que chaque événement hook produit.
Si vous avez besoin de faire apparaître les décisions de hook à votre application de manière fiable, journalisez-les séparément ou utilisez un canal de sortie dédié.
Ressources connexes
- Référence des hooks Claude Code : schémas JSON d’entrée/sortie complets, documentation des événements et motifs matcher
- Guide des hooks Claude Code : exemples de hooks de commande shell et procédures pas à pas
- Référence du SDK TypeScript : types de hook, définitions d’entrée/sortie et options de configuration
- Référence du SDK Python : types de hook, définitions d’entrée/sortie et options de configuration
- Permissions : contrôlez ce que votre agent peut faire
- Outils personnalisés : créez des outils pour étendre les capacités de l’agent