SpyBara
Go Premium

hooks.md 2026-10-02 22:59 UTC to 2026-10-03 16:02 UTC

This page contains 562 additions and 551 deletions.

2026
Thu 1 23:59 Fri 2 22:59 Sat 3 17:01

Référence des hooks

Référence pour les événements de hook Claude Code, le schéma de configuration, les formats d'entrée/sortie JSON, les codes de sortie, les hooks asynchrones, les hooks HTTP, les hooks de prompt et les hooks d'outils MCP.

Les hooks sont des commandes shell définies par l'utilisateur, des points de terminaison HTTP, des appels d'outils MCP, des prompts LLM ou des sous-agents qui s'exécutent automatiquement à des points spécifiques du cycle de vie de Claude Code. Claude Code déclenche les mêmes événements de hook partout où il s'exécute : les sessions dans le terminal, les extensions IDE, l'application de bureau et Claude Code sur le web. Utilisez cette référence pour consulter les schémas d'événements, les options de configuration, les formats d'entrée/sortie JSON et les fonctionnalités avancées comme les hooks asynchrones, les hooks HTTP et les hooks d'outils MCP.

Un plugin peut également enregistrer des hooks en tant que fonctions JavaScript que Claude Code appelle dans son propre processus, qui peuvent être dessinés dans l'interface ainsi qu'agir sur les événements. Un plugin qui le fait est un mod, et ces hooks de fonction sont couverts dans Réagir aux événements plutôt qu'ici. Les hooks sur cette page continuent de fonctionner aux côtés des mods.

Cycle de vie des hooks

Claude Code exécute les hooks à des points spécifiques pendant une session. Lorsqu'un événement se déclenche et qu'un matcher correspond, Claude Code transmet le contexte JSON de l'événement à votre gestionnaire de hook. Pour les hooks de commande, l'entrée arrive sur stdin. Pour les hooks HTTP, elle arrive dans le corps de la requête POST. Votre gestionnaire peut alors inspecter l'entrée, prendre une action et éventuellement retourner une décision.

Les événements se déclenchent selon trois cadences :

  • une fois par session : SessionStart et SessionEnd
  • une fois par tour : UserPromptSubmit, Stop et StopFailure
  • à chaque appel d'outil à l'intérieur de la boucle agentique : PreToolUse et PostToolUse, sauf les appels EndConversation, qui ignorent les deux
Diagramme du cycle de vie des hooks montrant Setup optionnel alimentant SessionStart, puis une boucle par tour contenant UserPromptSubmit, UserPromptExpansion pour les slash commands, la boucle agentique imbriquée (PreToolUse, PermissionRequest, PostToolUse, PostToolUseFailure, PostToolBatch, SubagentStart/Stop, TaskCreated, TaskCompleted), et Stop ou StopFailure, suivis de TeammateIdle, PreCompact, PostCompact et SessionEnd, avec Elicitation et ElicitationResult imbriqués dans l'exécution de l'outil MCP, PermissionDenied comme branche latérale de PermissionRequest pour les refus en mode auto, WorktreeCreate, WorktreeRemove, Notification, ConfigChange, InstructionsLoaded, CwdChanged, FileChanged et DirectoryAdded comme événements asynchrones autonomes, PreModelSwitch comme événement séquentiel autonome qui s'exécute avant un changement de modèle demandé, PostModelSwitch comme événement asynchrone autonome qui s'exécute après le changement du modèle de la session, et MessageDisplay comme événement d'affichage uniquement qui s'exécute pendant que le texte du message de l'assistant est diffusé en continu
<img src="https://mintcdn.com/claude-code/x7pO8l4XcvAXCoVc/images/hooks-lifecycle-dark.svg?fit=max&auto=format&n=x7pO8l4XcvAXCoVc&q=85&s=c9b3d88487335f58cce0b52e2f9e7531" className="hidden dark:block" alt="Diagramme du cycle de vie des hooks montrant Setup optionnel alimentant SessionStart, puis une boucle par tour contenant UserPromptSubmit, UserPromptExpansion pour les slash commands, la boucle agentique imbriquée (PreToolUse, PermissionRequest, PostToolUse, PostToolUseFailure, PostToolBatch, SubagentStart/Stop, TaskCreated, TaskCompleted), et Stop ou StopFailure, suivis de TeammateIdle, PreCompact, PostCompact et SessionEnd, avec Elicitation et ElicitationResult imbriqués dans l'exécution de l'outil MCP, PermissionDenied comme branche latérale de PermissionRequest pour les refus en mode auto, WorktreeCreate, WorktreeRemove, Notification, ConfigChange, InstructionsLoaded, CwdChanged, FileChanged et DirectoryAdded comme événements asynchrones autonomes, PreModelSwitch comme événement séquentiel autonome qui s'exécute avant un changement de modèle demandé, PostModelSwitch comme événement asynchrone autonome qui s'exécute après le changement du modèle de la session, et MessageDisplay comme événement d'affichage uniquement qui s'exécute pendant que le texte du message de l'assistant est diffusé en continu" width="520" height="1336" data-path="images/hooks-lifecycle-dark.svg" />

Le tableau ci-dessous résume le moment où chaque événement se déclenche. La section Événements de hook documente le schéma d'entrée complet et les options de contrôle de décision pour chacun.

Événement Quand il se déclenche
SessionStart Quand une session commence ou reprend
Setup Quand vous démarrez Claude Code avec --init-only, ou avec --init ou --maintenance en mode -p. Pour une préparation unique en CI ou dans les scripts
UserPromptSubmit Quand un prompt est soumis, avant que Claude le traite. Se déclenche également lors des tours que Claude Code démarre de lui-même
UserPromptExpansion Quand une commande tapée par l'utilisateur se développe en une invite, avant qu'elle n'atteigne Claude. Peut bloquer l'expansion
PreToolUse Avant qu'un appel d'outil s'exécute. Peut le bloquer
PermissionRequest Quand un appel d'outil nécessite une décision de permission
PermissionDenied Quand le mode automatique refuse un appel d'outil, y compris les refus sans verdict du classificateur. Utilisez la sortie JSON hookSpecificOutput.retry: true pour indiquer au modèle qu'il peut réessayer l'appel d'outil refusé. Claude Code ignore retry quand le classificateur n'a produit aucun verdict
PostToolUse Après qu'un appel d'outil réussisse
PostToolUseFailure Après qu'un appel d'outil échoue
PostToolBatch Après qu'un lot complet d'appels d'outils parallèles se résout, avant l'appel du modèle suivant
Notification Quand Claude Code envoie une notification
MessageDisplay Pendant que le texte du message assistant s'affiche
SubagentStart Quand un sous-agent est généré
SubagentStop Quand un sous-agent se termine
TaskCreated Quand une tâche est en cours de création via TaskCreate
TaskCompleted Quand une tâche est marquée comme complétée
Stop Quand Claude finit de répondre
StopFailure Quand le tour se termine en raison d'une erreur API
TeammateIdle Quand un coéquipier d'une équipe d'agents est sur le point de devenir inactif
InstructionsLoaded Quand un fichier CLAUDE.md ou .claude/rules/*.md est chargé dans le contexte. Se déclenche au démarrage de la session et quand les fichiers sont chargés paresseusement pendant une session
ConfigChange Quand un fichier de configuration change pendant une session
CwdChanged Quand le répertoire de travail change, par exemple quand Claude exécute une commande cd. Utile pour la gestion réactive de l'environnement avec des outils comme direnv
DirectoryAdded Quand un répertoire de travail est ajouté en milieu de session via /add-dir ou la demande de contrôle SDK register_repo_root
FileChanged Quand un fichier surveillé change sur le disque. Le champ matcher spécifie les noms de fichiers à surveiller
WorktreeCreate Quand un worktree est en cours de création via --worktree, isolation: "worktree", ou pour une session en arrière-plan. Remplace le comportement git par défaut
WorktreeRemove Quand un worktree est supprimé à la sortie de la session, quand un sous-agent se termine, ou quand vous supprimez une session en arrière-plan
PreCompact Avant la compaction du contexte
PostCompact Après la compaction du contexte est complétée
PreModelSwitch Avant que Claude Code applique un changement de modèle que vous ou un client avez demandé. Peut bloquer le changement
PostModelSwitch Après que le modèle de la session change, y compris les changements que Claude Code effectue de lui-même, comme la restauration du modèle quand vous reprenez une session
Elicitation Quand un serveur MCP demande une entrée utilisateur pendant un appel d'outil
ElicitationResult Après qu'un utilisateur réponde à une élicitation MCP, avant que la réponse soit renvoyée au serveur
SessionEnd Quand une session se termine

Comment un hook se résout

Pour voir comment l'événement, le matcher et le gestionnaire s'assemblent, considérez ce hook PreToolUse qui bloque les commandes shell destructrices.

Le matcher se limite aux appels d'outil Bash et la condition if se limite davantage aux sous-commandes Bash correspondant à rm *, donc block-rm.sh ne s'exécute que lorsque les deux filtres correspondent :

{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"if": "Bash(rm *)",
"command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/block-rm.sh",
"args": []
}
]
}
]
}
}

Le script lit l'entrée JSON depuis stdin, extrait la commande et retourne une permissionDecision de "deny" si elle contient rm -rf. Enregistrez-le dans .claude/hooks/block-rm.sh dans votre projet et rendez-le exécutable avec chmod +x .claude/hooks/block-rm.sh pour que Claude Code puisse l'exécuter :

#!/bin/bash
# .claude/hooks/block-rm.sh
COMMAND=$(jq -r '.tool_input.command')

if echo "$COMMAND" | grep -q 'rm -rf'; then
jq -n '{
hookSpecificOutput: {
hookEventName: "PreToolUse",
permissionDecision: "deny",
permissionDecisionReason: "Destructive command blocked by hook"
}
}'
else
exit 0  # no decision; normal permission flow applies
fi

Ce script, comme les autres exemples Bash sur cette page qui analysent l'entrée JSON, utilise jq, donc installez jq et assurez-vous qu'il se trouve sur votre PATH avant de les essayer.

Supposons maintenant que Claude Code décide d'exécuter Bash "rm -rf /tmp/build" par rapport à la configuration macOS/Linux. Voici ce qui se passe :

Diagramme de résolution du hook : PreToolUse se déclenche, le matcher vérifie la correspondance Bash, puis la condition if vérifie la correspondance Bash(rm *). Si les deux correspondent, la commande du hook s'exécute et retourne permissionDecision deny, donc l'appel d'outil est bloqué et Claude Code continue. Si l'une des vérifications ne correspond pas, le hook est ignoré et l'appel d'outil est autorisé à procéder. Diagramme de résolution du hook : PreToolUse se déclenche, le matcher vérifie la correspondance Bash, puis la condition if vérifie la correspondance Bash(rm *). Si les deux correspondent, la commande du hook s'exécute et retourne permissionDecision deny, donc l'appel d'outil est bloqué et Claude Code continue. Si l'une des vérifications ne correspond pas, le hook est ignoré et l'appel d'outil est autorisé à procéder.
1

L'événement se déclenche

L'événement PreToolUse se déclenche. Claude Code envoie l'entrée de l'outil en JSON sur stdin au hook :

{ "tool_name": "Bash", "tool_input": { "command": "rm -rf /tmp/build" }, ... }
2

Le matcher vérifie

Le matcher "Bash" correspond au nom de l'outil, donc ce groupe de hook s'active. Si vous omettez le matcher ou utilisez "*", le groupe s'active à chaque occurrence de l'événement.

3

La condition if vérifie

La condition if "Bash(rm *)" correspond car rm -rf /tmp/build est une sous-commande correspondant à rm *, donc ce gestionnaire s'exécute. Si la commande avait été npm test, la vérification if échouerait et block-rm.sh ne s'exécuterait jamais, évitant la surcharge de génération de processus. Le champ if est optionnel ; sans lui, chaque gestionnaire du groupe correspondant s'exécute.

4

Le gestionnaire de hook s'exécute

Le script inspecte la commande complète et trouve rm -rf, donc il imprime une décision sur stdout :

{
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "deny",
"permissionDecisionReason": "Destructive command blocked by hook"
}
}

Si la commande avait été une variante plus sûre de rm comme rm file.txt, le script aurait atteint exit 0 à la place. Un code de sortie 0 sans sortie signifie que le hook n'a pas de décision à signaler, donc l'appel d'outil continue à travers le flux de permission normal. Le hook peut refuser l'appel, mais rester silencieux ne l'approuve pas.

5

Claude Code agit sur le résultat

Claude Code lit la décision JSON, bloque l'appel d'outil et montre la raison à Claude.

La section Configuration ci-dessous documente le schéma complet, et chaque section événement de hook documente l'entrée que votre commande reçoit et la sortie qu'elle peut retourner.

Configuration

Les hooks sont définis dans les fichiers de paramètres JSON. La configuration a trois niveaux d'imbrication :

  1. Choisissez un événement de hook auquel répondre, comme PreToolUse ou Stop
  2. Ajoutez un groupe de matcher pour filtrer quand il se déclenche, comme « uniquement pour l'outil Bash »
  3. Définissez un ou plusieurs gestionnaires de hook à exécuter lorsqu'il y a correspondance

Consultez Comment un hook se résout ci-dessus pour une procédure pas à pas complète avec un exemple annoté.

Emplacements des hooks

L'endroit où vous définissez un hook détermine sa portée :

Emplacement Portée Partageable
~/.claude/settings.json Tous vos projets Non, local à votre machine
.claude/settings.json Projet unique Oui, peut être commité dans le repo
.claude/settings.local.json Projet unique Non, ignoré par git lorsque Claude Code enregistre un paramètre dedans
Paramètres de politique gérée À l'échelle de l'organisation Oui, contrôlé par l'administrateur
Plugin hooks/hooks.json Lorsque le plugin est activé Oui, fourni avec le plugin
Skill frontmatter Le reste de la session une fois que le skill est invoqué. Consultez Hooks dans les skills et agents Oui, défini dans le fichier du skill
Subagent frontmatter Pendant que ce subagent s'exécute Oui, défini dans le fichier du subagent

Les sessions cloud sur Claude Code sur le web ne lisent pas votre ~/.claude/settings.json local. Dans un environnement auto-hébergé, Claude Code exécute également les hooks que l'opérateur a ensemencés à partir du ~/.claude/ de l'hôte du runner, et il exécute les hooks dans le fichier de paramètres gérés de l'image du runner lorsque ce fichier figure parmi les sources gérées que Claude Code applique, ce qui par défaut signifie uniquement lorsque ni les paramètres gérés par le serveur ni une politique Claude Code livrée par MDM ne fournissent le niveau géré. Consultez ce qui se transfère de votre configuration pour savoir quels fichiers de paramètres et plugins, et donc quels hooks, atteignent une session cloud.

Pour plus de détails sur la résolution des fichiers de paramètres, consultez paramètres.

Les hooks des fichiers de paramètres, des paramètres de politique gérée et des plugins s'exécutent également à l'intérieur des subagents. Lorsqu'un subagent appelle un outil, les événements d'outil tels que PreToolUse et PostToolUse déclenchent les mêmes hooks configurés que dans la conversation principale, et l'entrée porte les champs d'entrée communs agent_id et agent_type qui identifient le subagent.

Les administrateurs peuvent utiliser allowManagedHooksOnly dans les paramètres gérés pour restreindre les hooks qui s'exécutent :

  • Vos hooks utilisateur, projet, local et plugin sont bloqués. Les hooks des plugins forcément activés dans les paramètres gérés enabledPlugins sont exempts
  • Claude Code restreint également votre statusLine, fileSuggestion et subagentStatusLine aux paramètres gérés
  • Claude Code désactive également les plugins avec une source command, y compris les plugins forcément activés dans les paramètres gérés enabledPlugins, sauf si disableCommandPluginSources est explicitement défini à false. Les sources command nécessitent Claude Code v2.1.229 ou ultérieur
  • Claude Code bloque également les commandes headersHelper du marketplace sauf si disableCommandPluginSources est explicitement défini à false, sauf pour un marketplace que les paramètres gérés eux-mêmes déclarent

Consultez ce qui s'exécute sous allowManagedHooksOnly.

Les entrées de hook fusionnent entre les niveaux de paramètres plutôt que de se remplacer mutuellement : les paramètres utilisateur, projet et local ajoutent leurs propres hooks sans supprimer les hooks gérés, et le paramètre disableAllHooks ne peut pas désactiver les hooks gérés en dehors des paramètres gérés.

Les listes blanches de hooks HTTP s'appliquent aux hooks de chaque source, y compris les paramètres de politique gérée :

  • allowedHttpHookUrls : lorsqu'il est défini à n'importe quel niveau de paramètres, Claude Code exécute un gestionnaire de hook HTTP uniquement si son URL correspond à la liste blanche fusionnée
  • httpHookAllowedEnvVars : lorsqu'il est défini, Claude Code n'interpose que les variables d'environnement de cette liste dans les en-têtes de hook

Modèles de matcher

Le champ matcher filtre quand les hooks se déclenchent. La façon dont un matcher est évalué dépend des caractères qu'il contient :

Valeur du matcher Évalué comme Exemple
"*", "" ou omis Correspondre à tous se déclenche à chaque occurrence de l'événement
Uniquement des lettres, des chiffres, _, -, des espaces, , et | Chaîne exacte ou liste de chaînes exactes séparées par | ou , avec espaces blancs optionnels autour Bash correspond uniquement à l'outil Bash ; Edit|Write et Edit, Write correspondent chacun à l'un ou l'autre outil exactement ; code-reviewer correspond uniquement à ce type d'agent
Contient tout autre caractère Expression régulière JavaScript, non ancrée ^Notebook correspond à tout outil commençant par Notebook ; mcp__memory__.* correspond à chaque outil du serveur memory

Un matcher sur le chemin de l'expression régulière est testé avec RegExp.prototype.test de JavaScript, qui réussit sur une correspondance n'importe où dans la valeur. Edit.* correspond à la fois à Edit et à NotebookEdit ; enveloppez le modèle dans ^ et $, comme dans ^Edit$, lorsque vous avez besoin d'une correspondance de chaîne entière.

FileChanged et StopFailure utilisent un ensemble de correspondance exacte plus étroit contenant uniquement des lettres, des chiffres, _ et |. Un trait d'union, un espace ou une virgule dans un matcher pour ces deux événements le maintient sur le chemin de l'expression régulière, et seul | sépare les alternatives. Tous les autres événements avec support de matcher dans le tableau qui suit acceptent | ou ,.

L'événement FileChanged ne suit pas ces règles lors de la construction de sa liste de surveillance. Consultez FileChanged.

Chaque type d'événement correspond sur un champ différent :

Événement Ce que le matcher filtre Exemples de valeurs de matcher
PreToolUse, PostToolUse, PostToolUseFailure, PermissionRequest, PermissionDenied nom de l'outil Bash, Edit|Write, mcp__.*
SessionStart comment la session a démarré startup, resume, clear, compact, fork
Setup quel drapeau CLI a déclenché la configuration init, maintenance
SessionEnd pourquoi la session s'est terminée clear, resume, logout, prompt_input_exit, other
Notification type de notification permission_prompt, idle_prompt, auth_success, elicitation_dialog, elicitation_url_dialog, elicitation_complete, elicitation_response, agent_needs_input, agent_completed, quota_auto_resume_fired, quota_auto_resume_stale, quota_auto_resume_disabled
SubagentStart type d'agent general-purpose, Explore, Plan, noms d'agents personnalisés ou noms limités au plugin comme ^my-plugin:reviewer$
PreCompact, PostCompact ce qui a déclenché la compaction manual, auto
PreModelSwitch, PostModelSwitch nom canonique du modèle vers lequel la session bascule, comme décrit sous PreModelSwitch claude-opus-5, claude-opus-4-6|claude-opus-5, .*opus.*
SubagentStop type d'agent mêmes valeurs que SubagentStart
ConfigChange source de configuration user_settings, project_settings, local_settings, policy_settings, skills
CwdChanged pas de support de matcher se déclenche toujours à chaque changement de répertoire
DirectoryAdded comment le répertoire a été ajouté slash_command, register_repo_root
FileChanged noms de fichiers littéraux à surveiller (consultez FileChanged) .envrc|.env
StopFailure type d'erreur rate_limit, overloaded, authentication_failed, oauth_org_not_allowed, account_on_hold, billing_error, invalid_request, model_not_found, server_error, max_output_tokens, cloud_credential_error, unknown
InstructionsLoaded raison du chargement session_start, nested_traversal, path_glob_match, include, compact
UserPromptExpansion nom de la commande vos noms de skill ou de commande
Elicitation nom du serveur MCP vos noms de serveur MCP configurés
ElicitationResult nom du serveur MCP mêmes valeurs que Elicitation
UserPromptSubmit, PostToolBatch, Stop, TeammateIdle, TaskCreated, TaskCompleted, WorktreeCreate, WorktreeRemove, MessageDisplay pas de support de matcher se déclenche toujours à chaque occurrence

Correspondre à StopFailure sur cloud_credential_error nécessite Claude Code v2.1.267 ou ultérieur, la première version qui signale les échecs de chargement des identifiants sous cette valeur plutôt que server_error ou unknown.

Pour la plupart des événements, Claude Code évalue le matcher par rapport à un champ de l'entrée JSON qu'il envoie à votre hook sur stdin. Pour les événements d'outil, ce champ est tool_name. Pour PreModelSwitch et PostModelSwitch, Claude Code évalue le matcher par rapport au nom canonique qu'il dérive de to_model, comme décrit sous PreModelSwitch. Chaque section événement de hook liste l'ensemble complet des valeurs de matcher et le schéma d'entrée pour cet événement.

Cet exemple exécute un script de linting uniquement lorsque Claude écrit ou édite un fichier :

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "/path/to/lint-check.sh"
          }
        ]
      }
    ]
  }
}

Si vous ajoutez un champ matcher à un événement sans support de matcher, il est silencieusement ignoré.

Pour les événements d'outil, vous pouvez filtrer plus étroitement en définissant le champ if sur les gestionnaires de hook individuels. if utilise la syntaxe des règles de permission pour correspondre au nom de l'outil et aux arguments ensemble, donc "Bash(git *)" s'exécute lorsqu'une sous-commande quelconque de l'entrée Bash correspond à git * et "Edit(*.ts)" s'exécute uniquement pour les fichiers TypeScript.

Correspondre aux outils MCP

Les outils du serveur MCP apparaissent comme des outils réguliers dans les événements d'outil (PreToolUse, PostToolUse, PostToolUseFailure, PermissionRequest, PermissionDenied), vous pouvez donc les faire correspondre de la même manière que tout autre nom d'outil.

Les outils MCP suivent le modèle de nommage mcp__<server>__<tool>, par exemple :

  • mcp__memory__create_entities : outil de création d'entités du serveur Memory
  • mcp__filesystem__read_file : outil de lecture de fichier du serveur Filesystem
  • mcp__github__search_repositories : outil de recherche du serveur GitHub

Pour correspondre à chaque outil d'un serveur, ajoutez .* au préfixe du serveur. Le .* est requis : un matcher comme mcp__memory ou mcp__brave-search contient uniquement des caractères de correspondance exacte, donc il est comparé comme une chaîne exacte et ne correspond à aucun outil.

  • mcp__memory__.* correspond à tous les outils du serveur memory
  • mcp__brave-search__.* correspond à tous les outils d'un serveur dont le nom contient un trait d'union
  • mcp__.*__write.* correspond à tout outil dont le nom commence par write de n'importe quel serveur

Les outils d'un serveur MCP fourni par un plugin utilisent un segment de serveur limité qui inclut le nom du plugin : mcp__plugin_<plugin-name>_<server-name>__<tool>. Un matcher écrit contre la clé de serveur nue ne se déclenche jamais pour ces outils. Pour un plugin nommé my-plugin qui regroupe un serveur sous la clé db, un outil query apparaît comme mcp__plugin_my-plugin_db__query, donc le matcher pour chaque outil de ce serveur est mcp__plugin_my-plugin_db__.*. Utilisez le même nom d'outil limité dans le champ if d'un gestionnaire. Consultez Serveurs MCP fournis par un plugin pour savoir comment le nom limité est construit.

Cet exemple enregistre toutes les opérations du serveur memory et valide les opérations d'écriture de n'importe quel serveur MCP :

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "mcp__memory__.*",
        "hooks": [
          {
            "type": "command",
            "command": "echo 'Memory operation initiated' >> ~/mcp-operations.log"
          }
        ]
      },
      {
        "matcher": "mcp__.*__write.*",
        "hooks": [
          {
            "type": "command",
            "command": "/home/user/scripts/validate-mcp-write.py"
          }
        ]
      }
    ]
  }
}

Champs du gestionnaire de hook

Chaque objet du tableau hooks interne est un gestionnaire de hook : la commande shell, le point de terminaison HTTP, l'outil MCP, le prompt LLM ou l'agent qui s'exécute lorsque le matcher correspond. Il y a cinq types :

  • Hooks de commande (type: "command") : exécutent une commande shell. Votre script reçoit l'entrée JSON de l'événement sur stdin et communique les résultats via les codes de sortie et stdout.
  • Hooks HTTP (type: "http") : envoient l'entrée JSON de l'événement en tant que requête HTTP POST à une URL. Le point de terminaison communique les résultats via le corps de la réponse en utilisant le même format de sortie JSON que les hooks de commande.
  • Hooks de l'outil MCP (type: "mcp_tool") : appellent un outil sur un serveur MCP configuré. La sortie textuelle de l'outil est traitée comme stdout d'un hook de commande.
  • Hooks de prompt (type: "prompt") : envoient un prompt à un modèle Claude pour une évaluation en un seul tour. Le modèle retourne sa décision en JSON. Consultez Hooks basés sur des prompts.
  • Hooks d'agent (type: "agent") : lancent un subagent qui peut utiliser des outils comme Read, Grep et Glob pour vérifier les conditions avant de retourner une décision. Les hooks d'agent sont expérimentaux et peuvent changer. Consultez Hooks basés sur des agents.

Tous les hooks correspondants s'exécutent en parallèle. Si vous définissez le même gestionnaire dans plus d'un fichier de paramètres, il s'exécute une fois. Une copie du même gestionnaire d'un plugin ou d'un skill reste séparée.

Les gestionnaires s'exécutent dans le répertoire courant avec l'environnement de Claude Code. Si le répertoire courant n'existe plus, par exemple un worktree ou un répertoire temporaire qu'un autre shell a supprimé en cours de session, Claude Code exécute les hooks de commande à partir du premier de ceux-ci qui existe toujours : le répertoire dans lequel la session a démarré, la racine du projet, votre répertoire personnel ou le répertoire temporaire du système. Claude Code enregistre un avertissement nommant le répertoire de secours dans le journal de débogage.

La variable d'environnement $CLAUDE_CODE_REMOTE est "true" dans les environnements web distants et n'est pas définie dans le CLI local. Claude Code v2.1.199 et ultérieur définit $CLAUDE_CODE_BRIDGE_SESSION_ID à l'ID de session Contrôle à distance tandis que la session locale a une connexion Contrôle à distance active.

Champs communs

Ces champs s'appliquent à tous les types de hooks :

Champ Requis Description
type oui "command", "http", "mcp_tool", "prompt" ou "agent"
if non Syntaxe de règle de permission pour filtrer quand ce hook s'exécute, comme "Bash(git *)" ou "Edit(*.ts)". Le hook de commande ne s'exécute que si l'appel d'outil correspond au modèle. Consultez le tableau de correspondance Bash ci-dessous pour voir comment les modèles Bash s'évaluent par rapport aux sous-commandes, $() et aux backticks. Évalué uniquement sur les événements d'outil : PreToolUse, PostToolUse, PostToolUseFailure, PermissionRequest et PermissionDenied. Sur les autres événements, un hook avec if défini ne s'exécute jamais. Utilise la même syntaxe que les règles de permission
timeout non Secondes avant annulation. Claude Code ne l'applique pas sur un hook de commande que vous exécutez avec async: true. Valeurs par défaut : 600 pour command, http et mcp_tool ; 30 pour prompt ; 60 pour agent. Claude Code abaisse la valeur par défaut de command, http et mcp_tool à 30 sur UserPromptSubmit, PreModelSwitch et PostModelSwitch, et à 10 sur MessageDisplay. Les hooks SessionEnd partagent un budget de 1,5 seconde ; si vos paramètres définissent un timeout par hook plus long, Claude Code augmente le budget pour correspondre, jusqu'à 60 secondes
statusMessage non Message de spinner personnalisé affiché pendant l'exécution du hook
once non Si true, Claude Code supprime le hook après sa première exécution réussie. Une exécution qui échoue, bloque avec le code de sortie 2 ou expire laisse le hook en place, donc il s'exécute à nouveau au prochain événement correspondant. Honoré uniquement pour les hooks déclarés dans le frontmatter des skills ; ignoré dans les fichiers de paramètres et le frontmatter des agents

Le champ if contient exactement une règle de permission. Il n'y a pas de syntaxe &&, || ou de liste pour combiner les règles ; pour appliquer plusieurs conditions, définissez un gestionnaire de hook séparé pour chacune.

Dans une condition if pour un outil de fichier, un modèle de répertoire à un seul segment comme "Edit(src/**)" correspond uniquement au répertoire src dans le répertoire de travail et aux fichiers sous celui-ci. Pour correspondre à un répertoire nommé src à n'importe quelle profondeur, écrivez "Edit(**/src/**)". Avant v2.1.214, "Edit(src/**)" correspondait à un répertoire nommé src à n'importe quelle profondeur sous le répertoire de travail.

Pour les modèles Bash, le fait que votre commande de hook s'exécute dépend de la forme du modèle et de la commande Bash que Claude invoque. Les affectations VAR=value en début sont supprimées avant la correspondance.

Modèle if Commande Bash Le hook s'exécute-t-il ? Pourquoi
Bash(git *) FOO=bar git push oui les affectations en début sont supprimées ; git push correspond
Bash(git *) npm test && git push oui chaque sous-commande est vérifiée ; git push correspond
Bash(rm *) echo $(rm -rf /) oui les commandes à l'intérieur de $() et des backticks sont vérifiées ; rm -rf / correspond
Bash(rm *) echo $(date) non aucune sous-commande ne correspond à rm *
Bash(git push *) echo $(date) oui les modèles qui spécifient plus que le nom de la commande exécutent le hook de toute façon sur $(), les backticks ou $VAR

Lorsque Claude Code ne peut pas déterminer quelles commandes l'entrée Bash exécute, il exécute votre hook indépendamment du modèle. Parce que le filtre if est au mieux un effort, utilisez le système de permission plutôt qu'un hook pour appliquer une autorisation ou un refus strict.

Champs des hooks de commande

En plus des champs communs, les hooks de commande acceptent ces champs :

Champ Requis Description
command oui Commande shell à exécuter. Avec args, l'exécutable à lancer directement. Consultez Forme exec et forme shell
args non Liste d'arguments. Lorsqu'elle est présente, command est résolu comme un exécutable et lancé directement avec args comme vecteur d'arguments, sans shell. Consultez Forme exec et forme shell
async non Si true, s'exécute en arrière-plan sans bloquer. Consultez Exécuter les hooks en arrière-plan
asyncRewake non Si true, s'exécute en arrière-plan et réveille Claude au code de sortie 2. Le stderr du hook, ou stdout s'il est vide, est affiché à Claude comme un rappel système afin qu'il puisse réagir à un échec en arrière-plan de longue durée
shell non Shell à utiliser pour ce hook. Accepte "bash" ou "powershell". Par défaut "bash", ou "powershell" sur Windows lorsque Git Bash n'est pas installé. Définir "powershell" exécute la commande via PowerShell sur Windows. Ne nécessite pas CLAUDE_CODE_USE_POWERSHELL_TOOL puisque les hooks lancent PowerShell directement. Ignoré lorsque args est défini
Forme exec et forme shell

Un hook de commande s'exécute en forme exec lorsque args est défini, et en forme shell lorsque args est omis. Définissez args chaque fois que le hook référence un placeholder de chemin, puisque chaque élément est passé comme un argument sans guillemets. Omettez args lorsque vous avez besoin de fonctionnalités shell comme les pipes ou &&, ou lorsqu'aucune de ces préoccupations ne s'applique.

Forme exec s'exécute lorsque args est présent. Claude Code résout command comme un exécutable sur PATH et le lance directement avec args comme vecteur d'arguments. Il n'y a pas de shell, donc chaque élément args est un argument exactement tel qu'écrit, et les placeholders de chemin comme ${CLAUDE_PLUGIN_ROOT} sont substitués dans command et dans chaque élément args comme des chaînes brutes. Les caractères spéciaux tels que les apostrophes, $ et les backticks passent verbatim car il n'y a pas de shell pour les interpréter. Aucune tokenisation shell ne se produit sur aucune plateforme.

Forme shell s'exécute lorsque args est absent. La chaîne command est passée à un shell : sh -c sur macOS et Linux, Git Bash sur Windows, ou PowerShell lorsque Git Bash n'est pas installé. Définissez le champ shell pour choisir explicitement. Le shell tokenise la chaîne, développe les variables et interprète les pipes, &&, les redirections et les globs.

Cet exemple exécute un script Node fourni avec un plugin. La forme exec passe le chemin du script résolu comme un argument sans guillemets :

{
  "type": "command",
  "command": "node",
  "args": ["${CLAUDE_PLUGIN_ROOT}/scripts/format.js", "--fix"]
}

La forme shell équivalente a besoin de guillemets pour gérer les chemins avec des espaces ou des caractères spéciaux :

{
  "type": "command",
  "command": "node \"${CLAUDE_PLUGIN_ROOT}\"/scripts/format.js --fix"
}

Les deux formes supportent les mêmes placeholders de chemin, et les deux les exportent comme variables d'environnement CLAUDE_PROJECT_DIR, CLAUDE_PLUGIN_ROOT et CLAUDE_PLUGIN_DATA sur le processus lancé, donc un script peut lire process.env.CLAUDE_PLUGIN_ROOT indépendamment de la façon dont il a été lancé.

Les hooks de plugin substituent également les valeurs ${user_config.*}, en forme exec uniquement : la valeur est substituée dans command et dans chaque élément args comme une chaîne brute, donc aucun shell ne la réanalyse.

Un hook de plugin en forme shell dont la command référence ${user_config.*} échoue avec une erreur au lieu de s'exécuter. Pour utiliser une valeur d'option à partir d'un hook en forme shell, lisez la variable d'environnement $CLAUDE_PLUGIN_OPTION_<KEY>, comme $CLAUDE_PLUGIN_OPTION_WEBHOOK_URL pour une option webhook_url, ou définissez args pour basculer le hook en forme exec. Avant v2.1.207, les commandes de hook de plugin en forme shell substituaient également ${user_config.*}.

Champs des hooks HTTP

En plus des champs communs, les hooks HTTP acceptent ces champs :

Champ Requis Description
url oui URL vers laquelle envoyer la requête POST
headers non En-têtes HTTP supplémentaires sous forme de paires clé-valeur. Les valeurs supportent l'interpolation de variables d'environnement en utilisant la syntaxe $VAR_NAME ou ${VAR_NAME}. Seules les variables listées dans allowedEnvVars sont résolues
allowedEnvVars non Liste des noms de variables d'environnement qui peuvent être interpolés dans les valeurs d'en-tête. Les références aux variables non listées sont remplacées par des chaînes vides. Requis pour que l'interpolation de variables d'environnement fonctionne

Claude Code envoie l'entrée JSON du hook en tant que corps de la requête POST avec Content-Type: application/json. Le corps de la réponse utilise le même format de sortie JSON que les hooks de commande.

La gestion des erreurs diffère des hooks de commande ; consultez Gestion des réponses HTTP.

Cet exemple envoie les événements PreToolUse à un service de validation local, en s'authentifiant avec un token de la variable d'environnement MY_TOKEN :

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "http",
            "url": "http://localhost:8080/hooks/pre-tool-use",
            "timeout": 30,
            "headers": {
              "Authorization": "Bearer $MY_TOKEN"
            },
            "allowedEnvVars": ["MY_TOKEN"]
          }
        ]
      }
    ]
  }
}

Champs des hooks de l'outil MCP

En plus des champs communs, les hooks de l'outil MCP acceptent ces champs :

Champ Requis Description
server oui Nom d'un serveur MCP configuré. Pour un serveur fourni par un plugin, c'est le nom limité plugin:<plugin-name>:<server-name>, comme plugin:my-plugin:db, pas la clé de serveur nue
tool oui Nom de l'outil à appeler sur ce serveur
input non Arguments passés à l'outil. Les valeurs de chaîne supportent la substitution ${path} de l'entrée JSON du hook, comme "${tool_input.file_path}"

Cet exemple appelle l'outil security_scan sur le serveur MCP my_server après chaque Write ou Edit, en passant le chemin du fichier édité :

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "mcp_tool",
            "server": "my_server",
            "tool": "security_scan",
            "input": { "file_path": "${tool_input.file_path}" }
          }
        ]
      }
    ]
  }
}
Comment le résultat de l'outil est lu

Claude Code lit le contenu textuel de l'outil de la même manière qu'il lit stdout d'un hook de commande, en suivant la règle d'analyse sous le code de sortie 0. Si l'outil retourne isError: true, le hook produit une erreur non-bloquante et l'exécution continue.

Quand le serveur est encore en cours de connexion

Sur les événements où un hook peut bloquer ou changer le résultat, tels que PreToolUse ou Stop, Claude Code attend qu'un serveur se connecte avant d'appeler l'outil, pendant au maximum MCP_TIMEOUT et dans le timeout du hook lui-même. Sur les événements observationnels, tels que Notification ou SessionEnd, il n'attend pas.

Un serveur affichant le statut cached se connecte lorsque le hook appelle son outil. Si le serveur n'est pas connecté à ce moment, le hook produit une erreur non-bloquante et l'exécution continue. Le hook ne démarre jamais un flux OAuth, donc authentifiez le serveur à partir de /mcp d'abord.

Événements qui se déclenchent avant que les serveurs MCP ne soient disponibles

SessionStart au lancement, y compris avec --continue ou --resume, et chaque événement Setup se déclenchent avant que les serveurs MCP de la session ne soient disponibles pour les hooks. Claude Code ignore leurs hooks mcp_tool sans appeler l'outil, et le journal de débogage enregistre mcp_tool hooks are not available for the 'SessionStart' hook event (no MCP client context), ou le même message nommant Setup. Lorsque SessionStart se déclenche à nouveau plus tard dans la session, après /clear ou une compaction, ses hooks mcp_tool s'exécutent. Pour tout ce dont la session a besoin au lancement, utilisez un hook type: "command" sur SessionStart à la place.

Champs des hooks de prompt et d'agent

En plus des champs communs, les hooks de prompt et d'agent acceptent ces champs :

Champ Requis Description
prompt oui Texte du prompt à envoyer au modèle. Utilisez $ARGUMENTS comme placeholder pour l'entrée JSON du hook. Échappez avec une barre oblique inverse pour inclure du texte littéral : \$1.00 s'affiche comme $1.00
model non Modèle à utiliser pour l'évaluation. Par défaut le modèle que Claude Code utilise pour la fonctionnalité en arrière-plan

Référencer les scripts par chemin

Utilisez ces placeholders pour référencer les scripts de hook par rapport à la racine du projet ou du plugin, indépendamment du répertoire de travail lorsque le hook s'exécute :

  • ${CLAUDE_PROJECT_DIR} : la racine du projet où la session a démarré. Claude Code définit également cette variable dans l'environnement des serveurs MCP stdio et des serveurs LSP de plugin.
  • ${CLAUDE_PLUGIN_ROOT} : le répertoire d'installation du plugin, pour les scripts fournis avec un plugin. Consultez variables d'environnement du plugin pour savoir comment le chemin se comporte lors des mises à jour.
  • ${CLAUDE_PLUGIN_DATA} : le répertoire de données persistantes du plugin, pour les dépendances et l'état qui doivent survivre aux mises à jour du plugin.

Préférez la forme exec pour tout hook qui référence un placeholder de chemin. En forme shell, enveloppez chaque placeholder entre guillemets doubles.

Cet exemple utilise ${CLAUDE_PROJECT_DIR} pour exécuter un vérificateur de style à partir du répertoire .claude/hooks/ du projet après tout appel d'outil Write ou Edit :

{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/check-style.sh",
"args": []
}
]
}
]
}
}

Hooks dans les skills et agents

En plus des fichiers de paramètres et des plugins, les hooks peuvent être définis directement dans les skills et les subagents en utilisant le frontmatter, dans le même format de configuration que les hooks basés sur les paramètres. La durée pendant laquelle Claude Code les garde enregistrés dépend du composant :

  • Hooks de subagent : Claude Code les exécute uniquement pendant que ce subagent s'exécute et les supprime lorsqu'il se termine. Claude Code convertit un hook Stop ici en SubagentStop, l'événement qu'il déclenche lorsqu'un subagent se termine.
  • Hooks de skill : Claude Code les enregistre lorsque vous ou Claude invoquez le skill et continue à les exécuter pour le reste de la session, sur les tours après le tour du skill lui-même. Pour que Claude Code supprime un hook après sa première exécution réussie à la place, définissez once: true sur celui-ci.

Ce skill définit un hook PreToolUse qui exécute un script de validation de sécurité avant chaque commande Bash :

---
name: secure-operations
description: Perform operations with security checks
hooks:
  PreToolUse:
    - matcher: "Bash"
      hooks:
        - type: command
          command: "./scripts/security-check.sh"
---

Les subagents utilisent le même format dans leur frontmatter YAML.

Les hooks de frontmatter dans un skill de projet suivent la même règle de confiance de l'espace de travail que les hooks dans les fichiers de paramètres. Claude Code les enregistre lorsque vous ou Claude invoquez le skill, y compris dans une exécution -p dans un dossier que vous n'avez pas approuvé.

Les hooks de frontmatter dans un subagent de projet s'exécutent uniquement après que vous acceptiez le dialogue de confiance de l'espace de travail pour le dossier d'où provient le fichier de l'agent. Une session -p ne compte pas comme l'accepter. Ce qui s'exécute avant que vous approuviez un dossier compare cela avec la règle du fichier de paramètres, et la page des subagents liste quels scopes sont exempts. Avant v2.1.218, ces hooks pouvaient s'exécuter à partir de dossiers que vous n'aviez pas approuvés.

Le menu `/hooks`

Tapez /hooks dans Claude Code pour ouvrir un navigateur en lecture seule pour vos hooks configurés. La liste étiquette chaque hook avec sa provenance, comme les paramètres utilisateur, les paramètres du projet, les paramètres locaux, un plugin ou la session actuelle.

Sélectionnez un hook pour voir le texte complet de ce qu'il exécute et l'endroit où il est défini, comme le chemin de son fichier de paramètres ou le nom de son plugin.

Pour parcourir tous les événements de hook, y compris ceux pour lesquels aucun hook n'est configuré, sélectionnez All events à la fin de la liste.

Désactiver ou supprimer les hooks

Pour supprimer un hook défini dans un fichier de paramètres, supprimez son entrée de ce fichier.

Pour désactiver temporairement tous les hooks sans les supprimer, définissez "disableAllHooks": true dans votre fichier de paramètres. Claude Code lit la valeur restante après que la précédence des paramètres s'applique, donc un "disableAllHooks": false dans le .claude/settings.json d'un projet remplace un true dans vos paramètres utilisateur. Pour désactiver les hooks pour une exécution quelle que soit la configuration du projet, passez --settings '{"disableAllHooks": true}', qui prend la précédence sur les paramètres du projet et locaux. Il n'y a aucun moyen de désactiver un hook individuel tout en le gardant dans la configuration.

Le paramètre disableAllHooks respecte la hiérarchie des paramètres gérés. Si un administrateur a configuré des hooks via les paramètres de politique gérée, disableAllHooks défini dans les paramètres utilisateur, projet ou local ne peut pas désactiver ces hooks gérés. Seul disableAllHooks défini au niveau des paramètres gérés peut désactiver les hooks gérés. Pour la portée complète de chaque niveau, consultez disableAllHooks.

Les éditions directes des hooks dans les fichiers de paramètres sont normalement détectées automatiquement par le moniteur de fichiers.

Entrée et sortie des hooks

Les hooks de commande reçoivent les données JSON via stdin et communiquent les résultats via les codes de sortie, stdout et stderr. Les hooks HTTP reçoivent le même JSON que le corps de la requête POST et communiquent les résultats via le corps de la réponse HTTP. Cette section couvre les champs et le comportement communs à tous les événements. Chaque section d'événement sous Événements de hook inclut son schéma d'entrée spécifique et les options de contrôle de décision.

Sur macOS et Linux, les hooks de commande s'exécutent dans leur propre session sans terminal de contrôle. Le processus de hook et tous les processus enfants ne peuvent pas ouvrir /dev/tty ou envoyer des séquences d'échappement directement à l'interface Claude Code. Windows n'a pas de /dev/tty.

Pour afficher un message à l'utilisateur sur n'importe quelle plateforme, retournez systemMessage dans la sortie JSON. Certains événements le rejettent ou le livrent ailleurs, et chaque section d'événement le précise. Pour déclencher une notification de bureau, définir un titre de fenêtre ou sonner la cloche, retournez terminalSequence à la place.

Champs d'entrée communs

Les événements de hook reçoivent ces champs en JSON, en plus des champs spécifiques à l'événement documentés dans chaque section événement de hook. Pour les hooks de commande, ce JSON arrive via stdin. Pour les hooks HTTP, il arrive dans le corps de la requête POST.

Champ Description
session_id Identifiant de session actuel
prompt_id UUID identifiant le prompt utilisateur actuellement traité. Correspond à l'attribut prompt.id sur les événements OpenTelemetry, afin que vous puissiez corréler la sortie du hook avec la télémétrie pour un seul prompt. Absent jusqu'à la première entrée utilisateur. Nécessite Claude Code v2.1.196 ou ultérieur
transcript_path Chemin vers le JSON de conversation. Le fichier de transcription est écrit de manière asynchrone et peut être en retard par rapport à la conversation en mémoire, il se peut donc qu'il n'inclue pas encore les messages les plus récents du tour actuel lorsqu'un hook se déclenche. Les hooks qui ont besoin du texte final de l'assistant du tour actuel doivent utiliser last_assistant_message sur Stop et SubagentStop au lieu de lire la transcription
cwd Répertoire de travail courant lorsque le hook est invoqué
scratchpad_dir Chemin vers le répertoire scratchpad de la session, où Claude conserve les fichiers de travail temporaires. Absent lorsque la session n'a pas de scratchpad ou que le répertoire temporaire n'est pas disponible. Nécessite Claude Code v2.1.257 ou ultérieur
permission_mode Mode de permission actuel : "default", "plan", "acceptEdits", "auto", "dontAsk" ou "bypassPermissions". Le mode étiqueté Manuel arrive comme "default", jamais comme "manual", afin que les scripts qui correspondent à "default" continuent de fonctionner. Tous les événements ne reçoivent pas ce champ. Consultez l'exemple JSON de chaque événement de hook
effort Objet avec un champ level contenant le niveau d'effort en vigueur lorsque le hook s'exécute : "low", "medium", "high", "xhigh" ou "max". Si vous définissez un niveau que le modèle actif ne supporte pas, level rapporte le niveau que Claude Code a exécuté à la place ; Ajuster le niveau d'effort explique comment il choisit ce niveau. L'objet correspond au champ effort de la ligne de statut. Présent pour les événements qui se déclenchent dans un contexte d'utilisation d'outil, tels que PreToolUse, PostToolUse, Stop et SubagentStop, lorsque le modèle actuel supporte le paramètre d'effort. Le niveau est également disponible pour les commandes de hook et l'outil Bash en tant que variable d'environnement $CLAUDE_EFFORT.
hook_event_name Nom de l'événement qui s'est déclenché

Lors de l'exécution avec --agent ou à l'intérieur d'un subagent, deux champs supplémentaires sont inclus :

Champ Description
agent_id Identifiant unique pour le subagent. Présent uniquement lorsque le hook se déclenche à l'intérieur d'un appel de subagent. Utilisez ceci pour distinguer les appels de hook de subagent des appels du thread principal.
agent_type Nom de l'agent (par exemple, "Explore" ou "security-reviewer"). Présent lorsque la session utilise --agent ou que le hook se déclenche à l'intérieur d'un subagent. Pour les subagents, le type du subagent prend précédence sur la valeur --agent de la session. Consultez SubagentStart pour les valeurs que les subagents personnalisés et fournis par un plugin rapportent et comment écrire un matcher contre un nom scoped du plugin.

Seuls les hooks SessionStart peuvent recevoir un champ model, et Claude Code ne l'inclut pas toujours. Les hooks PreModelSwitch et PostModelSwitch reçoivent from_model et to_model à la place, utilisez donc un hook PostModelSwitch pour suivre le modèle au fur et à mesure qu'il change pendant une session.

Il n'y a pas de variable d'environnement $CLAUDE_MODEL. Le hook peut lire $ANTHROPIC_MODEL si vous la définissez dans votre shell, mais cette valeur ne change pas lorsque vous changez de modèle avec /model pendant une session.

Un processus de hook hérite de l'environnement parent, à l'exception des variables d'exportateur OTEL_* que Claude Code supprime de chaque sous-processus qu'il génère et, lorsque CLAUDE_CODE_SUBPROCESS_ENV_SCRUB est défini sur 1, les variables qu'il supprime.

Par exemple, un hook PreToolUse pour une commande Bash reçoit ceci sur stdin :

{
  "session_id": "abc123",
  "prompt_id": "550e8400-e29b-41d4-a716-446655440000",
  "transcript_path": "/home/user/.claude/projects/.../transcript.jsonl",
  "cwd": "/home/user/my-project",
  "scratchpad_dir": "/tmp/claude-1000/-home-user-my-project/abc123/scratchpad",
  "permission_mode": "default",
  "hook_event_name": "PreToolUse",
  "tool_name": "Bash",
  "tool_input": {
    "command": "npm test",
    "description": "Run test suite",
    "timeout": 120000,
    "run_in_background": false
  },
  "tool_use_id": "toolu_01ABC123..."
}

Les champs tool_name, tool_input et tool_use_id sont spécifiques à l'événement. Chaque section événement de hook documente les champs supplémentaires pour cet événement.

Sortie du code de sortie

Le code de sortie de votre commande de hook indique à Claude Code si l'action doit procéder, être bloquée ou être ignorée. Le code de sortie n'agit pas seul. Claude Code lit les champs de sortie JSON depuis stdout sur chaque code de sortie, pas seulement 0, et pour les événements qui utilisent le modèle de décision standard, un objet analysé qui passe la validation du schéma prend effet aux côtés du code. Le blocage d'exit 2 est le seul résultat que JSON ne peut pas remplacer.

Deux tableaux possèdent les exceptions par événement : Comportement du code de sortie 2 par événement dit ce que les codes de sortie font pour chaque événement, et Contrôle de décision dit quels champs de décision chaque événement honore. Les champs universels tels que systemMessage fonctionnent sur la plupart des événements et sont listés dans le tableau Sortie JSON.

Exit code 0

Exit 0 signifie succès, et c'est le code de sortie prévu lorsque vous imprimez JSON pour un contrôle structuré.

Pour la plupart des événements, Claude Code écrit stdout dans le journal de débogage et ne l'affiche pas dans la transcription. Les exceptions sont UserPromptSubmit, UserPromptExpansion, SessionStart et PostModelSwitch, où Claude Code ajoute stdout en texte brut comme contexte que Claude peut voir et sur lequel agir.

Que Claude Code lise votre stdout comme sortie JSON ou comme texte brut dépend de la façon dont il commence et se termine, en ignorant les espaces blancs environnants :

  • Commence par { et se termine par } : Claude Code l'analyse comme JSON. Lorsque la sortie est deux lignes ou plus qui s'analysent chacune comme JSON seules, et aucune ligne n'est un objet sortie JSON qui définit un champ, Claude Code traite la sortie entière comme du texte brut. Lorsque l'une de ces lignes définit un champ, la sortie entière est un échec d'analyse, décrit ci-dessous.
  • Commence par { mais ne se termine pas par } : Claude Code le traite comme du texte brut.
  • Commence par n'importe quoi d'autre : Claude Code le traite comme du texte brut, un tableau JSON ou une chaîne JSON entre guillemets incluse.

Pour les événements qui utilisent le modèle de décision standard, exit 0 avec un objet analysé qui échoue la validation du schéma est une erreur non-bloquante : l'action procède, et la transcription affiche un avis <hook name> hook error avec le message de validation. La même chose se produit sur tout code de sortie autre que 2, tandis que exit 2 bloque toujours.

Pour les événements qui utilisent le modèle de décision standard, lorsque Claude Code essaie d'analyser votre stdout comme JSON et ne peut pas, il rapporte une erreur non-bloquante sur chaque code de sortie autre que 2. La transcription affiche un avis <hook name> hook error avec le message d'analyse. Sur les événements qui ajoutent stdout en texte brut comme contexte, Claude Code n'ajoute pas le texte. Avant v2.1.248, Claude Code traitait ce stdout comme du texte brut.

Stderr d'un hook qui quitte 0 va uniquement au journal de débogage, jamais à la transcription, et Claude ne le voit jamais. Pour le lire vous-même, activez la journalisation de débogage. Pour afficher un avertissement à Claude à partir d'un hook PostToolUse ou PostToolUseFailure, quittez 2 à la place afin que Claude voie stderr même si l'outil a déjà s'exécuté.

Exit code 2

Exit 2 signifie une erreur bloquante. Sur les événements qui peuvent bloquer, exit 2 bloque que vous imprimiez JSON ou non : même une permissionDecision JSON de "allow" ne peut pas la remplacer. Claude Code lit toujours tout sortie JSON valide sur stdout. Sur Elicitation et ElicitationResult, le hookSpecificOutput d'un hook exit-2 est ignoré.

Le message de blocage est la raison de la décision de blocage de votre JSON lorsqu'elle en fait une, et votre texte stderr sinon. Ce que le blocage fait varie selon l'événement : PreToolUse bloque l'appel d'outil, UserPromptSubmit rejette le prompt, et ainsi de suite. Comportement du code de sortie 2 par événement énumère l'effet pour chaque événement, et chaque section d'événement dit où le message va.

Un hook qui quitte 2 tout en imprimant JSON qui échoue la validation du schéma sortie JSON bloque toujours : Claude Code utilise stderr comme raison de blocage et enregistre l'échec de validation dans le journal de débogage. Avant v2.1.214, Claude Code traitait cette combinaison comme une erreur non-bloquante et l'action procédait.

Ce script bloque les commandes rm en quittant 2 et laisse chaque autre commande au flux de permission normal :

#!/bin/bash
# Lit l'entrée JSON depuis stdin, vérifie la commande
input=$(cat)
command=$(jq -r '.tool_input.command' <<<"$input")

if [[ "$command" == rm* ]]; then
  echo "Blocked: rm commands are not allowed" >&2
  exit 2  # Erreur bloquante : l'appel d'outil est empêché
fi

exit 0  # Pas de décision : le flux de permission normal s'applique

Autres codes de sortie

Tout autre code de sortie ne bloque pas seul pour la plupart des événements de hook. Ce qui se passe dépend de votre stdout :

  • Avec un objet analysé qui passe la validation du schéma, pour les événements qui utilisent le modèle de décision standard, Claude Code ignore le code de sortie et le JSON seul décide du résultat :
    • Chaque champ que l'événement supporte est honoré, y compris permissionDecision, additionalContext, updatedInput et systemMessage, et le hook n'est pas signalé comme une erreur.
    • Contrôle de décision énumère les champs de décision par événement ; les champs universels comme systemMessage suivent le tableau Sortie JSON.
  • Avec un objet analysé qui échoue la validation du schéma, pour les événements qui utilisent le modèle de décision standard, c'est la même erreur non-bloquante que sur exit 0 : l'action procède, et l'avis <hook name> hook error porte le message de validation.
  • Avec stdout que Claude Code essaie d'analyser comme JSON et ne peut pas, Claude Code rapporte la même erreur non-bloquante que sur exit 0 pour les événements qui utilisent le modèle de décision standard. L'action procède, et l'avis porte le message d'analyse.
  • Avec stdout que Claude Code traite comme du texte brut, ou avec stdout vide, c'est une erreur non-bloquante pour la plupart des événements de hook : l'action procède, et la transcription affiche un avis <hook name> hook error suivi de la première ligne de stderr, préfixée par Failed with non-blocking status code:. Pour capturer le stderr complet, activez la journalisation de débogage.

Les événements en dehors du modèle de décision standard gardent leurs propres lignes dans le tableau par événement : WorktreeCreate échoue la création sur tout code de sortie non-zéro peu importe ce que votre JSON dit, et les événements qui rejettent complètement la sortie du hook, comme StopFailure, ignorent votre JSON sur chaque code de sortie, à part les champs d'effet secondaire comme terminalSequence, qui se déclenchent toujours.

Un hook qui ne peut pas démarrer atterrit dans le même bucket non-bloquant. Lorsque le chemin du script n'existe pas ou n'est pas exécutable, le shell quitte avec un code comme 127 et vous voyez le même avis avec le message de l'interpréteur, par exemple Failed with non-blocking status code: /bin/sh: /path/to/hook.sh: No such file or directory. Pour la plupart des événements de hook, l'action procède. Lorsque vous configurez un hook de politique, regardez cet avis à sa première exécution : un chemin mal orthographié dans settings.json laisse la porte silencieusement désactivée.

Délais d'expiration

À l'exception d'un hook de commande que vous exécutez avec async: true, Claude Code annule un hook command, http ou mcp_tool qui atteint son timeout, en rejetant la sortie du hook, donc sur la plupart des événements un hook expiré ne rend aucune décision.

Sur PreModelSwitch, un hook annulé à son délai d'expiration bloque le changement de modèle. Sur PreToolUse, les deux familles de hooks diffèrent :

  • Un hook command, http ou mcp_tool expiré ne bloque pas l'appel d'outil. L'appel continue via le flux de permission normal, donc ne comptez pas sur un hook bloqué pour agir comme une porte.
  • Un hook de rappel Agent SDK qui dépasse son délai d'expiration bloque l'appel d'outil.

Comportement du code de sortie 2 par événement

Exit code 2 est la façon dont un hook signale « arrêtez, ne faites pas cela ». L'effet dépend de l'événement, car certains événements représentent des actions qui peuvent être bloquées (comme un appel d'outil qui ne s'est pas encore produit) et d'autres représentent des choses qui se sont déjà produites ou ne peuvent pas être empêchées.

Événement de hook Peut bloquer ? Ce qui se passe sur exit 2
PreToolUse Oui Bloque l'appel d'outil
PermissionRequest Non Exit code 2 n'est pas honoré pour cet événement et le flux de permission procède inchangé. Refusez via l'objet decision à la place
UserPromptSubmit Oui Bloque le prompt, il ne parvient jamais à Claude. Consultez Ce qu'un prompt bloqué laisse derrière
UserPromptExpansion Oui Bloque l'expansion
Stop Oui Empêche Claude de s'arrêter, continue la conversation
SubagentStop Oui Empêche le subagent de s'arrêter
TeammateIdle Oui Empêche le coéquipier de devenir inactif, le coéquipier continue de travailler
TaskCreated Oui Annule la création de la tâche
TaskCompleted Oui Empêche la tâche d'être marquée comme complétée
ConfigChange Oui Bloque la modification de configuration de prendre effet (sauf policy_settings)
StopFailure Non La sortie et le code de sortie sont ignorés, sauf terminalSequence
PostToolUse Non Affiche stderr à Claude ; l'outil a déjà s'exécuté
PostToolUseFailure Non Affiche stderr à Claude ; l'outil a déjà échoué
PostToolBatch Oui Arrête la boucle agentique avant l'appel du modèle suivant
PermissionDenied Non Exit code et stderr sont ignorés car le refus a déjà eu lieu. Utilisez JSON hookSpecificOutput.retry: true pour indiquer au modèle qu'il peut réessayer ; Claude Code ignore retry: true pour les refus sans verdict
Notification Non Exit code et stderr sont ignorés
SubagentStart Non Affiche stderr à l'utilisateur uniquement
SessionStart Non Affiche stderr à l'utilisateur uniquement
Setup Non Exit code et stderr sont ignorés
SessionEnd Non Affiche stderr à l'utilisateur uniquement
CwdChanged Non Affiche stderr à l'utilisateur uniquement
DirectoryAdded Non Stderr va au journal de débogage ; le répertoire est déjà ajouté
FileChanged Non Affiche stderr à l'utilisateur uniquement
PreCompact Oui Bloque la compaction
PostCompact Non Affiche stderr à l'utilisateur uniquement
PreModelSwitch Oui Bloque le changement de modèle et affiche stderr à l'utilisateur
PostModelSwitch Non Affiche stderr à l'utilisateur uniquement ; le modèle a déjà changé
Elicitation Oui Refuse l'élicitation
ElicitationResult Oui Bloque la réponse (l'action devient decline)
WorktreeCreate Oui Tout code de sortie non-zéro provoque l'échec de la création du worktree
WorktreeRemove Oui Tout code de sortie non-zéro rend la suppression du worktree échouée si le répertoire existe toujours après. Consultez WorktreeRemove pour ce qui arrive au répertoire
InstructionsLoaded Non Exit code est ignoré
MessageDisplay Non Le texte original est affiché

Pour SessionStart, SubagentStart et PostModelSwitch, Claude Code rend le stderr du code de sortie 2 dans la transcription comme un avis <hook name> hook error, de la même manière qu'il rend une erreur non-bloquante. Claude ne le voit pas, et la session ou le subagent procède. Pour SubagentStart, l'avis apparaît dans la propre transcription du subagent, pas dans la conversation parent.

Gestion des réponses HTTP

Les hooks HTTP utilisent les codes de statut HTTP et les corps de réponse au lieu des codes de sortie et stdout. Les résultats ci-dessous s'appliquent à la plupart des événements ; un événement avec son propre contrat d'échec dans le tableau par événement, tel que WorktreeCreate, applique ce contrat à un hook HTTP échoué aussi :

  • 2xx avec un corps vide : succès, équivalent à exit code 0 sans sortie
  • 2xx avec un corps d'objet JSON : analysé en utilisant le même schéma sortie JSON que les hooks de commande. Un corps qui échoue la validation du schéma est une erreur non-bloquante
  • 2xx avec n'importe quel autre corps, comme du texte brut : erreur non-bloquante, gérée de la même manière qu'un statut non-2xx. Claude Code n'ajoute pas le texte au contexte de Claude
  • Statut non-2xx : erreur non-bloquante, l'exécution continue
  • Défaillance de connexion : erreur non-bloquante, l'exécution continue
  • Délai d'expiration : le hook est annulé, comme décrit sous Délais d'expiration

Contrairement aux hooks de commande, les hooks HTTP ne peuvent pas signaler une erreur bloquante uniquement via les codes de statut. Pour bloquer un appel d'outil ou refuser une permission, retournez une réponse 2xx avec un corps JSON contenant les champs de décision appropriés.

Sortie JSON

Les codes de sortie vous permettent uniquement de bloquer ou de rester silencieux, mais la sortie JSON vous donne un contrôle plus granulaire. Au lieu de quitter avec le code 2 pour bloquer, quittez 0 et imprimez un objet JSON sur stdout. Claude Code lit les champs spécifiques de ce JSON pour contrôler le comportement, y compris contrôle de décision pour bloquer, autoriser ou escalader à l'utilisateur.

La sortie stdout de votre hook doit contenir uniquement l'objet JSON. Si votre profil shell imprime du texte au démarrage, cela peut interférer avec l'analyse JSON. Consultez Hook JSON has no effect dans le guide de dépannage.

Les chaînes de sortie du hook, y compris additionalContext, systemMessage et initialUserMessage, et son stdout brut, sont plafonnées à 10 000 caractères :

  • Portée : Claude Code mesure chaque chaîne seule, même lorsque plusieurs hooks s'exécutent pour le même événement. Pour la sortie JSON, chaque champ est mesuré séparément ; stdout brut est mesuré dans son ensemble.
  • Au-delà de la limite : Claude Code enregistre la sortie dans un fichier du répertoire de session et la remplace par le chemin du fichier et un aperçu de jusqu'à 2 000 premiers caractères. Un grand résultat Bash valide est géré de la même manière, décrit sous Output limits. Contrairement à ce plafond Bash, ce cap n'a pas de paramètre ou de variable d'environnement pour l'augmenter.
  • Lecture du fichier : Claude Code ne demande pas à Claude de lire le fichier, donc gardez tout ce que Claude doit toujours voir dans le cap.

L'objet JSON supporte trois types de champs :

  • Champs universels comme continue sont listés dans le tableau ci-dessous. Chaque événement les accepte, mais certains événements les rejettent ou livrent systemMessage ailleurs que dans la transcription. Chaque section d'événement le précise. terminalSequence fonctionne sur ces événements aussi, avec les exceptions listées sous Émettre des notifications de terminal.
  • decision et reason au niveau supérieur sont utilisés par certains événements pour bloquer ou fournir des commentaires.
  • hookSpecificOutput est un objet imbriqué pour les événements qui ont besoin d'un contrôle plus riche. Il nécessite un champ hookEventName défini au nom de l'événement.
Champ Par défaut Description
continue true Si false, Claude arrête complètement le traitement après l'exécution du hook. Prend précédence sur tous les champs de décision spécifiques à l'événement
stopReason aucun Message affiché à l'utilisateur lorsque continue est false. Il reste dans la conversation, afin que Claude le voie si la conversation continue
suppressOutput false N'a aucun effet : Claude Code accepte le champ mais n'agit pas dessus. La sortie stdout d'un hook réussi n'est jamais affichée dans la transcription et est enregistrée dans le journal de débogage
systemMessage aucun Message d'avertissement affiché à l'utilisateur. Dans Agent SDK et --output-format stream-json sortie, il peut arriver comme un SDKInformationalMessage
terminalSequence aucun Une séquence d'échappement de terminal pour Claude Code d'émettre en votre nom, comme une notification de bureau, un titre de fenêtre ou une cloche. Restreint aux OSC 0/1/2/9/99/777 et BEL. Si la valeur contient quelque chose en dehors de la liste blanche, le champ est ignoré. Utilisez ceci au lieu d'écrire sur /dev/tty, qui n'est pas disponible pour les hooks

Pour arrêter Claude entièrement :

{ "continue": false, "stopReason": "Build failed, fix errors before continuing" }

Pour les hooks PreToolUse et PostToolUse, l'arrêt s'applique même lorsque l'appel d'outil échoue ou se termine tandis que Claude diffuse toujours une réponse.

Émettre des notifications de terminal

Les hooks s'exécutent sans terminal de contrôle, donc écrire des séquences d'échappement directement sur /dev/tty échoue. À la place, retournez la séquence d'échappement dans le champ terminalSequence et Claude Code l'émet pour vous via son propre chemin d'écriture de terminal. C'est sans course, fonctionne à l'intérieur de tmux et GNU screen, et fonctionne sur Windows où il n'y a pas de /dev/tty.

Le champ accepte une chaîne d'une ou plusieurs séquences d'échappement en liste blanche :

  • OSC 0, 1, 2 : titres de fenêtre et d'icône
  • OSC 9 : notifications iTerm2, ConEmu, Windows Terminal et WezTerm, y compris la progression de la barre des tâches 9;4
  • OSC 99 : notifications Kitty
  • OSC 777 : notifications urxvt, Ghostty et Warp
  • BEL nu

Les séquences peuvent être terminées avec BEL ou avec ST. Tout ce qui est en dehors de la liste blanche, y compris les séquences de curseur CSI et les séquences de couleur, les séquences de palette OSC, les hyperliens OSC 8, les écritures de presse-papiers OSC 52 et OSC 1337, est rejeté et le champ est ignoré.

Claude Code écrit la séquence elle-même lorsqu'il traite la sortie de votre hook, donc le champ fonctionne sur les événements qui rejettent systemMessage et continue, tels que Notification et StopFailure. Il a deux limites :

  • Claude Code écrit la séquence uniquement dans une session interactive, et uniquement tandis que son interface est à l'écran. En mode non-interactif avec le drapeau -p et dans l'Agent SDK, il ignore le champ.
  • Un hook de commande WorktreeCreate ne peut pas retourner JSON, car Claude Code lit son stdout comme le chemin du worktree. Un hook HTTP WorktreeCreate retourne JSON et peut inclure le champ.

L'exemple ci-dessous déclenche une notification de bureau à partir d'un hook Notification. La séquence d'échappement est construite avec des échappements octaux printf afin que les octets de contrôle n'apparaissent jamais sur la ligne de commande shell, et jq -n --arg construit la sortie JSON afin que les guillemets, les barres obliques inverses et les sauts de ligne dans le message de notification soient correctement échappés :

#!/bin/bash
# Hook de notification : ping le bureau lorsque Claude Code a besoin d'attention.
input=$(cat)
title="Claude Code"
body=$(jq -r '.message // "Needs your attention"' <<<"$input")
seq=$(printf '\033]777;notify;%s;%s\007' "$title" "$body")
jq -nc --arg seq "$seq" '{terminalSequence: $seq}'

La forme { "terminalSequence": "..." } est la même à partir de n'importe quel shell ou langage.

Ajouter du contexte pour Claude

Le champ additionalContext transmet une chaîne de votre hook dans la fenêtre de contexte de Claude. Claude Code enveloppe la chaîne dans un rappel système et l'insère dans la conversation au point où le hook s'est déclenché. Claude lit le rappel lors de la prochaine demande du modèle, mais il n'apparaît pas comme un message de chat dans l'interface.

Retournez additionalContext à l'intérieur de hookSpecificOutput aux côtés du nom de l'événement :

{
  "hookSpecificOutput": {
    "hookEventName": "PostToolUse",
    "additionalContext": "This file is generated. Edit src/schema.ts and run `bun generate` instead."
  }
}

L'endroit où le rappel apparaît dépend de l'événement :

Lorsque plusieurs hooks retournent additionalContext pour le même événement, Claude reçoit toutes les valeurs.

Si une valeur dépasse 10 000 caractères, Claude Code écrit le texte dans un fichier du répertoire de session et transmet à Claude le chemin du fichier avec un aperçu de jusqu'à 2 000 premiers caractères à la place. Claude peut lire le fichier, mais Claude Code ne le demande pas.

Utilisez additionalContext pour les informations que Claude devrait connaître sur l'état actuel de votre environnement ou l'opération qui vient de s'exécuter :

  • État de l'environnement : la branche actuelle, la cible de déploiement ou les drapeaux de fonctionnalité actifs
  • Règles de projet conditionnelles : quelle commande de test s'applique au fichier qui vient d'être modifié, quels répertoires sont en lecture seule dans ce worktree
  • Données externes : problèmes ouverts qui vous sont assignés, résultats CI récents, contenu récupéré à partir d'un service interne

Pour les instructions qui ne changent jamais, préférez CLAUDE.md. Il se charge sans exécuter de script et est l'endroit standard pour les conventions de projet statiques.

Écrivez le texte sous forme de déclarations factuelles plutôt que d'instructions système impératives. Des formulations telles que « La cible de déploiement est production » ou « Ce repo utilise bun test » se lisent comme des informations de projet. Le texte encadré comme des commandes système hors bande peut déclencher les défenses contre l'injection de prompt de Claude, ce qui amène Claude à vous présenter le texte au lieu de le traiter comme du contexte.

Claude Code enregistre le texte injecté dans la transcription de session. Pour les événements mid-session comme PostToolUse ou UserPromptSubmit, lorsque vous reprenez avec --continue ou --resume, Claude Code rejoue le texte enregistré plutôt que de réexécuter le hook pour les tours passés, de sorte que les valeurs comme les horodatages ou les SHA de commit deviennent obsolètes. Les hooks SessionStart s'exécutent à nouveau à la reprise avec source défini sur "resume", ou "fork" si vous avez ajouté --fork-session, afin qu'ils puissent actualiser leur contexte.

Contrôle de décision

Tous les événements ne supportent pas le blocage ou le contrôle du comportement via JSON. Les événements qui le font utilisent chacun un ensemble différent de champs pour exprimer cette décision. Utilisez ce tableau comme référence rapide avant d'écrire un hook :

Événements Modèle de décision Champs clés
UserPromptSubmit, UserPromptExpansion, PostToolUse, PostToolUseFailure, PostToolBatch, Stop, SubagentStop, ConfigChange, PreCompact decision au niveau supérieur decision: "block", reason. Stop et SubagentStop acceptent également hookSpecificOutput.additionalContext pour les commentaires non-erreur qui continuent la conversation
TeammateIdle, TaskCompleted Exit code ou continue: false Exit code 2 bloque l'action avec commentaires stderr. JSON {"continue": false, "stopReason": "..."} arrête également complètement le coéquipier, correspondant au comportement du hook Stop ; TaskCompleted l'ignore lorsque l'outil TaskUpdate a déclenché l'événement
TaskCreated Exit code ou decision au niveau supérieur Exit code 2 ou decision: "block" annule la tâche et retourne le message à Claude. continue: false est ignoré
PreToolUse hookSpecificOutput permissionDecision (allow/deny/ask/defer), permissionDecisionReason
PreModelSwitch hookSpecificOutput ou decision au niveau supérieur permissionDecision (allow/deny/ask), permissionDecisionReason. decision: "block" annule également le changement
PermissionRequest hookSpecificOutput decision.behavior (allow/deny)
PermissionDenied hookSpecificOutput retry: true indique au modèle qu'il peut réessayer l'appel d'outil refusé ; Claude Code l'ignore pour les refus sans verdict
WorktreeCreate retour de chemin Le hook de commande imprime le chemin sur stdout ; le hook HTTP retourne hookSpecificOutput.worktreePath. L'échec du hook ou l'absence de chemin échoue la création
WorktreeRemove Exit code Tout code de sortie non-zéro rend la suppression échouée si le répertoire existe toujours après. La sortie JSON est rejetée
Elicitation hookSpecificOutput action (accept/decline/cancel), content (valeurs des champs de formulaire pour accept)
ElicitationResult hookSpecificOutput action (accept/decline/cancel), content (valeurs des champs de formulaire override)
MessageDisplay hookSpecificOutput displayContent remplace le texte affiché à l'écran. Affichage uniquement : la transcription et ce que Claude voit conservent l'original
SessionStart, SubagentStart, PostModelSwitch Contexte uniquement hookSpecificOutput.additionalContext ajoute du contexte pour Claude. SessionStart accepte également initialUserMessage, watchPaths, sessionTitle et reloadSkills. Pas de blocage ou de contrôle de décision
Setup, Notification, SessionEnd, PostCompact, InstructionsLoaded, StopFailure, CwdChanged, DirectoryAdded, FileChanged Aucun Pas de contrôle de décision. Utilisé pour les effets secondaires comme la journalisation ou le nettoyage

Quelques événements peuvent également réécrire le contenu plutôt que seulement l'autoriser ou le bloquer :

Pour les cas d'usage de rédaction ou de transformation, interceptez à PreToolUse pour les entrées d'outil sortantes et PostToolUse pour les résultats d'outil entrants.

Voici des exemples de chaque modèle en action :

La seule valeur pour decision est "block". Pour autoriser l'action à procéder, omettez decision de votre JSON, ou quittez 0 sans aucun JSON :

{
"decision": "block",
"reason": "Test suite must pass before proceeding"
}

Pour des exemples étendus incluant la validation de commandes Bash, le filtrage de prompts et les scripts d'approbation automatique, consultez Ce que vous pouvez automatiser dans le guide et la implémentation de référence du validateur de commandes Bash.

Événements de hook

Chaque événement correspond à un point du cycle de vie de Claude Code où des hooks peuvent s'exécuter. Les sections ci-dessous suivent l'ordre du cycle de vie : de la configuration de la session à la fin de la session, en passant par la boucle agentique. Chaque section décrit quand l'événement se déclenche, quels matchers il prend en charge, l'entrée JSON qu'il reçoit et comment contrôler le comportement via la sortie.

SessionStart

S'exécute lorsque Claude Code démarre une nouvelle session ou reprend une session existante. Utile pour charger du contexte de développement, comme les issues existantes ou les modifications récentes de votre base de code, ou pour définir des variables d'environnement. Pour un contexte statique qui ne nécessite pas de script, utilisez plutôt CLAUDE.md.

SessionStart s'exécute à chaque session, gardez donc ces hooks rapides. Seuls les hooks type: "command" et type: "mcp_tool" sont pris en charge. Consultez Champs des hooks d'outil MCP pour savoir quand les hooks mcp_tool s'exécutent.

La valeur du matcher correspond à la manière dont la session a été initiée :

Matcher Quand il se déclenche
startup Nouvelle session
resume --resume, --continue ou /resume
clear /clear
compact Compaction automatique ou manuelle
fork Une nouvelle session dérivée d'une session existante : --fork-session avec --resume ou --continue, la copie en arrière-plan de /fork, /branch, ou une conversation que vous déplacez en arrière-plan

Avant la v2.1.214, les sessions dérivées indiquaient la source "resume".

Lorsque vous démarrez une session interactive, reprenez une conversation au lancement avec --continue ou --resume, ou exécutez /clear, les hooks SessionStart s'exécutent en arrière-plan. Vous pouvez saisir du texte immédiatement, et une conversation que vous avez reprise s'affiche sans attendre les hooks. La première réponse de Claude attend toujours la fin des hooks, afin que leur contexte parvienne à Claude.

Lorsque vous changez de conversation avec /resume au sein d'une session, le changement attend au contraire la fin des hooks. Si vous exécutez /clear ou passez à une autre conversation alors que des hooks en arrière-plan sont encore en cours d'exécution, rien de ce qu'ils renvoient ne s'applique à la session.

La même attente s'applique au lancement, y compris pour une session reprise : un prompt que vous envoyez pendant que les hooks SessionStart sont encore en cours d'exécution ne parvient à Claude qu'une fois ceux-ci terminés.

Pendant l'une ou l'autre attente, appuyez sur Esc pour ramener le prompt dans la zone de saisie sans l'envoyer. Les hooks continuent de s'exécuter.

Entrée de SessionStart

En plus des champs d'entrée communs, les hooks SessionStart reçoivent source et, facultativement, model, agent_type et session_title :

Champ Description
source Comment la session a démarré : "startup" pour les nouvelles sessions, "resume" pour les sessions reprises, "clear" après /clear, "compact" après une compaction, ou "fork" pour une nouvelle session dérivée d'une session existante
model L'identifiant du modèle actif. Il peut être omis, par exemple après /clear ou lorsqu'une session est restaurée via la récupération de conversation ; vérifiez donc la présence du champ avant de le lire
agent_type Le nom de l'agent, présent lorsque vous démarrez Claude Code avec claude --agent <name>
session_title Le titre personnalisé de la session, présent lorsqu'il est défini, par exemple avec --name, /rename, la sortie sessionTitle d'un hook, ou renameSession() de l'Agent SDK. Un hook qui émet sessionTitle peut d'abord vérifier ce champ pour éviter d'écraser un titre personnalisé existant

Une session que vous n'avez pas nommée peut tout de même avoir un titre généré. Ce titre n'est pas un titre personnalisé et n'apparaît pas dans session_title.

Lorsque source vaut "resume" ou "fork" et que la transcription contient au moins une réponse de Claude, les hooks SessionStart reçoivent également les quatre champs ci-dessous. Votre hook peut les utiliser pour indiquer ce que coûte la reprise d'une conversation ancienne avant la première requête, par exemple dans un systemMessage. Ces champs nécessitent Claude Code v2.1.251 ou ultérieure.

Champ Description
seconds_since_last_response Secondes réelles écoulées depuis la dernière réponse dans la transcription reprise
context_tokens Tokens que la première requête de la session reprise renvoie comme prompt
prompt_cache_likely_expired true lorsque la dernière réponse est plus ancienne que la durée de vie du cache de prompt de la session ou qu'une compaction ultérieure a remplacé la conversation mise en cache
estimated_cache_write_usd Coût estimé en dollars américains de l'écriture de context_tokens dans le cache de prompt sur le modèle de la session, hors réponse

Cet exemple montre l'entrée pour une session reprise 90 minutes après sa dernière réponse :

{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "cwd": "/Users/...",
  "hook_event_name": "SessionStart",
  "source": "resume",
  "model": "claude-opus-5",
  "seconds_since_last_response": 5400,
  "context_tokens": 182340,
  "prompt_cache_likely_expired": true,
  "estimated_cache_write_usd": 1.1396
}

Contrôle de décision de SessionStart

Claude Code ajoute au contexte de Claude la sortie stdout qu'il traite comme du texte brut. En plus des champs de sortie JSON disponibles pour tous les hooks, vous pouvez renvoyer ces champs propres à l'événement :

Champ Description
additionalContext Chaîne ajoutée au contexte de Claude au début de la conversation, avant le premier prompt. Consultez Ajouter du contexte pour Claude pour savoir comment le texte est transmis et ce qu'il faut y mettre
initialUserMessage Chaîne utilisée comme premier message utilisateur de la session. S'applique en mode non interactif avec le flag -p, où elle devient le premier tour même si aucun prompt n'est fourni. Si un prompt est fourni, il suit comme tour suivant. Contrairement à additionalContext, qui s'attache à un tour existant, ce champ crée le tour
sessionTitle Définit le titre de la session, avec le même effet que /rename. Utilisez-le pour nommer automatiquement les sessions à partir du dossier de lancement, de la branche git ou du nom du worktree. S'applique lorsque source vaut "startup", "resume" ou "fork" ; ignoré pour "clear" et "compact"
watchPaths Tableau de chemins absolus à surveiller pour les événements FileChanged pendant cette session
reloadSkills Booléen. Lorsqu'il vaut true, Claude Code analyse à nouveau les répertoires de skills et de commandes une fois les hooks SessionStart terminés, de sorte que les skills installés par le hook sont disponibles dans la même session, dès le premier prompt
{
  "hookSpecificOutput": {
    "hookEventName": "SessionStart",
    "additionalContext": "Current branch: feat/auth-refactor\nUncommitted changes: src/auth.ts, src/login.tsx\nActive issue: #4211 Migrate to OAuth2",
    "sessionTitle": "auth-refactor"
  }
}

Comme la sortie stdout brute parvient déjà à Claude pour cet événement, un hook qui ne fait que charger du contexte peut écrire directement sur stdout sans construire de JSON. Utilisez la forme JSON lorsque vous devez combiner du contexte avec d'autres champs tels que sessionTitle.

Utilisez reloadSkills lorsqu'un hook SessionStart installe ou met à jour des skills. La découverte des skills s'exécute normalement avant la fin des hooks SessionStart, de sorte que les fichiers que le hook écrit dans ~/.claude/skills/ ou .claude/skills/ n'apparaîtraient sinon que dans la session suivante. Cet exemple synchronise un dépôt de skills partagé et demande la nouvelle analyse :

#!/bin/bash

git -C ~/.claude/skills/team-skills pull --quiet 2>/dev/null || \
  git clone --quiet https://git.example.com/your-org/team-skills.git ~/.claude/skills/team-skills

echo '{"hookSpecificOutput": {"hookEventName": "SessionStart", "reloadSkills": true}}'

L'URL du dépôt est un espace réservé ; remplacez-la par votre propre dépôt de skills. Avec l'espace réservé, le clone échoue et affiche un message fatal: sur stderr. La sortie stderr d'un hook SessionStart qui se termine avec le code 0 est uniquement informative, donc la demande reloadSkills s'applique quand même.

Conserver les variables d'environnement

Les hooks SessionStart ont accès à la variable d'environnement CLAUDE_ENV_FILE, qui fournit un chemin de fichier dans lequel vous pouvez conserver des variables d'environnement pour les commandes Bash suivantes.

Pour définir des variables d'environnement individuelles, écrivez des instructions export dans CLAUDE_ENV_FILE. Utilisez l'ajout (>>) pour préserver les variables définies par d'autres hooks :

#!/bin/bash

if [ -n "$CLAUDE_ENV_FILE" ]; then
  echo 'export NODE_ENV=production' >> "$CLAUDE_ENV_FILE"
  echo 'export DEBUG_LOG=true' >> "$CLAUDE_ENV_FILE"
  echo 'export PATH="$PATH:./node_modules/.bin"' >> "$CLAUDE_ENV_FILE"
fi

exit 0

Pour capturer toutes les modifications d'environnement effectuées par des commandes de configuration, comparez les variables exportées avant et après :

#!/bin/bash

ENV_BEFORE=$(export -p | sort)

# Run your setup commands that modify the environment
source ~/.nvm/nvm.sh
nvm use 20

if [ -n "$CLAUDE_ENV_FILE" ]; then
  ENV_AFTER=$(export -p | sort)
  comm -13 <(echo "$ENV_BEFORE") <(echo "$ENV_AFTER") >> "$CLAUDE_ENV_FILE"
fi

exit 0

Setup

Se déclenche uniquement lorsque vous lancez Claude Code avec --init-only, ou avec --init ou --maintenance en mode non interactif avec le flag -p. Il ne se déclenche pas lors d'un démarrage normal. Utilisez-le pour une installation ponctuelle de dépendances ou un nettoyage planifié que vous déclenchez explicitement depuis la CI ou des scripts, indépendamment du démarrage normal de session. Pour une initialisation par session, utilisez plutôt SessionStart.

La valeur du matcher correspond au flag CLI qui a déclenché le hook :

Matcher Quand il se déclenche
init claude --init-only ou claude -p --init
maintenance claude -p --maintenance

Lorsque vous exécutez claude --init-only, Claude Code exécute les hooks Setup et les hooks SessionStart avec le matcher startup, puis se termine sans démarrer de conversation.

Lorsque vous démarrez ou poursuivez une conversation avec -p, vous devez également fournir un prompt, en argument ou transmis via stdin. Vous pouvez omettre le prompt lorsqu'un hook SessionStart fournit initialUserMessage ou lorsque vous reprenez une session avec un appel d'outil différé.

En cas de succès, --init-only n'affiche rien dans le terminal. Pour confirmer que les hooks se sont exécutés, lancez claude --debug-file <path> --init-only, en remplaçant <path> par l'emplacement d'un fichier de log, et recherchez dans le log les entrées des hooks Setup et SessionStart.

Comme Setup ne se déclenche pas à chaque lancement, un plugin qui a besoin d'une dépendance installée ne peut pas s'appuyer uniquement sur Setup. L'approche pratique consiste à vérifier la présence de la dépendance lors de la première utilisation et à l'installer si elle est absente, par exemple avec un hook ou un skill qui teste la présence de ${CLAUDE_PLUGIN_DATA}/node_modules et exécute npm install en son absence. Consultez le répertoire de données persistantes pour savoir où stocker les dépendances installées. Si vous distribuez votre plugin via une marketplace, vous n'aurez peut-être pas besoin de cette approche : Claude Code installe automatiquement les dépendances de packages Node.js éligibles lorsqu'il met le plugin en cache.

Entrée de Setup

En plus des champs d'entrée communs, les hooks Setup reçoivent un champ trigger défini sur "init" ou "maintenance" :

{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "cwd": "/Users/...",
  "hook_event_name": "Setup",
  "trigger": "init"
}

Contrôle de décision de Setup

Les hooks Setup ne peuvent pas bloquer ; l'exécution continue quel que soit le code de sortie. Quel que soit le code de sortie, Claude Code ignore les champs de sortie JSON d'un hook Setup, tels que systemMessage, continue et hookSpecificOutput.additionalContext. Avec -p, la sortie stdout, la sortie stderr et le code de sortie d'un hook Setup n'apparaissent dans la sortie de l'exécution que sous forme d'événements hook_response lorsque vous lancez avec --output-format stream-json --verbose.

Les hooks Setup ont accès à CLAUDE_ENV_FILE. Les variables écrites dans ce fichier persistent dans les commandes Bash suivantes de la session, comme dans les hooks SessionStart. Seuls les hooks type: "command" s'exécutent sur Setup. Un hook type: "mcp_tool" sur Setup est toujours ignoré, comme décrit dans Champs des hooks d'outil MCP.

InstructionsLoaded

Se déclenche lorsqu'un fichier CLAUDE.md ou .claude/rules/*.md est chargé dans le contexte. Cet événement se déclenche au démarrage de la session pour les fichiers chargés immédiatement, puis à nouveau plus tard lorsque des fichiers sont chargés à la demande, par exemple lorsque Claude accède à un sous-répertoire contenant un CLAUDE.md imbriqué ou lorsque des règles conditionnelles avec un frontmatter paths: correspondent. Le hook ne prend en charge ni le blocage ni le contrôle de décision. Il s'exécute de manière asynchrone à des fins d'observabilité.

Cet événement ne se déclenche pas lorsque Claude lit AGENTS.md directement via le paramètre Project instructions. Il se déclenche lorsqu'un CLAUDE.md importe votre AGENTS.md, avec load_reason défini sur include comme pour tout autre fichier importé, et lorsque CLAUDE.md est un lien symbolique vers celui-ci, comme un chargement normal de CLAUDE.md.

Le matcher s'applique à load_reason. Par exemple, utilisez "matcher": "session_start" pour ne déclencher le hook que pour les fichiers chargés au démarrage de la session, ou "matcher": "path_glob_match|nested_traversal" pour ne le déclencher que pour les chargements à la demande.

Entrée d'InstructionsLoaded

En plus des champs d'entrée communs, les hooks InstructionsLoaded reçoivent ces champs :

Champ Description
file_path Chemin absolu du fichier d'instructions chargé
memory_type Portée du fichier : "User", "Project", "Local" ou "Managed"
load_reason Raison du chargement du fichier : "session_start", "nested_traversal", "path_glob_match", "include" ou "compact". La valeur "compact" apparaît lorsque des fichiers d'instructions sont rechargés après un événement de compaction
globs Motifs glob de chemin issus du frontmatter paths: du fichier, le cas échéant. Présent uniquement pour les chargements path_glob_match
trigger_file_path Chemin du fichier dont l'accès a déclenché ce chargement, pour les chargements à la demande
parent_file_path Chemin du fichier d'instructions parent qui a inclus celui-ci, pour les chargements include
{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../transcript.jsonl",
  "cwd": "/Users/my-project",
  "hook_event_name": "InstructionsLoaded",
  "file_path": "/Users/my-project/CLAUDE.md",
  "memory_type": "Project",
  "load_reason": "session_start"
}

Contrôle de décision d'InstructionsLoaded

Les hooks InstructionsLoaded n'ont pas de contrôle de décision. Ils ne peuvent ni bloquer ni modifier le chargement des instructions. Claude Code ignore leurs champs de sortie JSON, tels que systemMessage et continue. Utilisez cet événement pour la journalisation d'audit, le suivi de conformité ou l'observabilité.

UserPromptSubmit

S'exécute lorsqu'un prompt est soumis, avant que Claude ne le traite. Cela vous permet d'ajouter du contexte supplémentaire en fonction du prompt ou de la conversation, de valider des prompts ou de bloquer certains types de prompts.

Les hooks UserPromptSubmit ne se déclenchent pas uniquement pour les prompts que vous tapez. Claude Code les exécute également lors :

Les hooks UserPromptSubmit ont un délai d'expiration par défaut de 30 secondes pour les types command, http et mcp_tool, plus court que la valeur par défaut de 600 secondes pour ces types sur la plupart des autres événements. Comme ce hook s'exécute avant chaque prompt et bloque le traitement par le modèle jusqu'à ce qu'il se termine, un hook bloqué paralyse la session. Si votre hook a besoin de plus de temps, définissez le champ timeout dans l'entrée du hook.

À l'exception d'un hook de commande que vous exécutez avec async: true, un hook de commande, HTTP ou d'outil MCP UserPromptSubmit qui atteint son délai d'expiration est annulé et sa sortie, y compris tout additionalContext, est ignorée. Le prompt parvient tout de même à Claude sans ce contexte. La transcription affiche un avis indiquant le nom du hook, le délai d'expiration atteint et le fait que la sortie a été ignorée.

Un hook callback de l'Agent SDK sur UserPromptSubmit qui atteint son délai d'expiration bloque le prompt avec un message indiquant le nom du hook et le délai d'expiration, car un callback à cet endroit peut jouer le rôle de garde-fou de politique qui ne doit pas laisser passer en cas d'échec. La session continue. Avant la v2.1.208, l'expiration d'un callback sur cet événement terminait le tour avec une erreur d'exécution.

Entrée d'UserPromptSubmit

En plus des champs d'entrée communs, les hooks UserPromptSubmit reçoivent le champ prompt contenant le texte soumis. Le contenu collé qui a été réduit en un espace réservé [Pasted text #N] arrive développé à sa place. Dans les sessions où Claude Code signale le texte collé à Claude, ce contenu développé se trouve entre une ligne <pasted_content id="…"> et une ligne </pasted_content id="…"> ; tenez donc compte de ces lignes si votre hook analyse le prompt.

Les hooks UserPromptSubmit reçoivent également session_title lorsque la session a un titre personnalisé, avec la même signification que le champ session_title de SessionStart.

{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "cwd": "/Users/...",
  "permission_mode": "default",
  "hook_event_name": "UserPromptSubmit",
  "prompt": "Write a function to calculate the factorial of a number"
}

Contrôle de décision d'UserPromptSubmit

Les hooks UserPromptSubmit peuvent contrôler si un prompt soumis est traité et ajouter du contexte. Tous les champs de sortie JSON sont disponibles.

Il existe deux façons d'ajouter du contexte à la conversation avec le code de sortie 0 :

  • Sortie stdout en texte brut : Claude Code ajoute au contexte de Claude la sortie stdout qu'il traite comme du texte brut
  • JSON avec additionalContext : utilisez le format JSON ci-dessous pour plus de contrôle. Le champ additionalContext est ajouté comme contexte

Aucun des deux canaux ne produit d'entrée visible dans la transcription. La sortie stdout brute et la valeur additionalContext sont chacune injectées sous forme de rappel système commençant par le nom du hook ; Claude lit les deux. Pour confirmer la transmission, consultez le log de débogage.

Pour bloquer un prompt, renvoyez un objet JSON avec decision défini sur "block" :

Champ Description
decision "block" arrête le prompt avant qu'il n'atteigne Claude. Omettez-le pour laisser le prompt se poursuivre
reason Affiché à l'utilisateur lorsque decision vaut "block". Non ajouté au contexte
additionalContext Chaîne ajoutée au contexte de Claude en plus du prompt soumis. Consultez Ajouter du contexte pour Claude
sessionTitle Définit le titre de la session. Utilisez-le pour nommer automatiquement les sessions en fonction du contenu du prompt
suppressOriginalPrompt Si true lorsque le hook bloque le prompt, le texte du prompt est exclu du message de blocage. Consultez Ce que laisse un prompt bloqué

Un hook qui bloque en se terminant avec le code 2 est traité de la même manière que reason : le message de blocage affiche le texte de stderr à l'utilisateur, et celui-ci n'est pas ajouté au contexte.

{
  "decision": "block",
  "reason": "Explanation for decision",
  "hookSpecificOutput": {
    "hookEventName": "UserPromptSubmit",
    "additionalContext": "My additional context here",
    "sessionTitle": "My session title",
    "suppressOriginalPrompt": true
  }
}

Ce que laisse un prompt bloqué

Un prompt bloqué n'atteint jamais Claude, mais son texte n'est pas supprimé partout. Par défaut, le message de blocage affiché à l'utilisateur se termine par Original prompt: suivi du texte soumis, et Claude Code écrit ce message dans le fichier de transcription de la session sur le disque. Pour exclure le texte du message, affichez un JSON avec "suppressOriginalPrompt": true dans hookSpecificOutput. Cela fonctionne que le hook bloque avec decision: "block" ou en se terminant avec le code 2. Un hook qui se termine avec le code 2 sans afficher de JSON inclut toujours le texte du prompt dans son message de blocage.

suppressOriginalPrompt ne modifie que le message de blocage. Le texte soumis peut toujours apparaître dans des fichiers locaux tels que la transcription de la session et votre historique de prompts ; un hook de blocage n'est donc pas un moyen d'empêcher qu'un secret soit écrit sur le disque. Pour limiter ou supprimer ces fichiers, consultez Stockage en texte brut et Effacer les données locales.

UserPromptExpansion

S'exécute lorsqu'une commande saisie par l'utilisateur se développe en prompt avant d'atteindre Claude. Utilisez-le pour empêcher l'invocation directe de commandes spécifiques, injecter du contexte pour un skill particulier ou journaliser les commandes invoquées par les utilisateurs. Par exemple, un hook correspondant à deploy peut bloquer /deploy sauf si un fichier d'approbation est présent, ou un hook correspondant à un skill de revue peut ajouter la checklist de revue de l'équipe en tant qu'additionalContext.

Cet événement couvre le chemin que PreToolUse ne couvre pas : un hook PreToolUse correspondant à l'outil Skill ne se déclenche que lorsque Claude appelle l'outil, mais saisir /skillname directement contourne PreToolUse. UserPromptExpansion se déclenche sur ce chemin direct.

Correspond à command_name. Laissez le matcher vide pour le déclencher sur chaque commande de type prompt.

Entrée d'UserPromptExpansion

En plus des champs d'entrée communs, les hooks UserPromptExpansion reçoivent expansion_type, command_name, command_args, command_source et la chaîne prompt d'origine. Le champ expansion_type vaut slash_command pour les skills et les commandes personnalisées, ou mcp_prompt pour les prompts de serveurs MCP.

{
  "session_id": "abc123",
  "transcript_path": "/Users/.../00893aaf.jsonl",
  "cwd": "/Users/...",
  "permission_mode": "default",
  "hook_event_name": "UserPromptExpansion",
  "expansion_type": "slash_command",
  "command_name": "example-skill",
  "command_args": "arg1 arg2",
  "command_source": "plugin",
  "prompt": "/example-skill arg1 arg2"
}

Contrôle de décision d'UserPromptExpansion

Les hooks UserPromptExpansion peuvent bloquer le développement ou ajouter du contexte. Tous les champs de sortie JSON sont disponibles.

Champ Description
decision "block" empêche le développement de la commande. Omettez-le pour la laisser se poursuivre
reason Affiché à l'utilisateur lorsque decision vaut "block"
additionalContext Chaîne ajoutée au contexte de Claude en plus du prompt développé. Consultez Ajouter du contexte pour Claude

Un hook qui bloque en se terminant avec le code 2 est traité de la même manière que reason : le message de blocage affiche le texte de stderr à l'utilisateur.

{
  "decision": "block",
  "reason": "This slash command is not available",
  "hookSpecificOutput": {
    "hookEventName": "UserPromptExpansion",
    "additionalContext": "Additional context for this expansion"
  }
}

MessageDisplay

S'exécute pendant qu'un message de l'assistant est diffusé à l'écran. Claude Code affiche le message par incréments : chaque fois qu'un lot de lignes nouvellement terminées est prêt à être affiché, le hook s'exécute une fois avec ces lignes et Claude Code affiche le texte de remplacement du hook à leur place. Un message long produit plusieurs appels ; un message court peut n'en produire qu'un seul.

Utilisez MessageDisplay pour :

  • supprimer le markdown pour un affichage minimal
  • transformer le texte qu'une application Agent SDK affiche à ses utilisateurs
  • masquer les clés API ou les noms d'hôtes internes dans les réponses de Claude

Claude Code retient chaque lot jusqu'à ce que votre hook renvoie une réponse, gardez donc le hook rapide. Si le hook échoue ou expire, Claude Code affiche le texte d'origine. Le délai d'expiration par défaut pour cet événement est de 10 secondes ; si votre hook a besoin de plus de temps, définissez le champ timeout dans l'entrée du hook.

MessageDisplay concerne uniquement l'affichage : le texte de remplacement ne modifie que ce qui est rendu à l'écran. La transcription et ce que voit Claude conservent le texte d'origine, de sorte que Claude ne voit jamais le remplacement, et le mode verbeux affiche l'original. Le hook ne reçoit que le texte des messages de l'assistant, donc les résultats d'outils et le texte que vous saisissez s'affichent sans modification.

MessageDisplay ne prend pas en charge les matchers et se déclenche pour chaque message de l'assistant qui diffuse du texte ; les messages sans texte, comme les réponses ne contenant que des appels d'outils, ne le déclenchent pas.

Dans les exécutions non interactives, y compris les requêtes Agent SDK et claude -p, MessageDisplay s'exécute une fois par message de l'assistant au lieu d'une fois par lot de lignes. L'appel unique arrive une fois le message terminé et contient le texte complet du message : index vaut 0, final vaut true, et delta contient l'intégralité du message. Un hook qui collecte le texte delta de chaque message reçoit le même texte total dans les deux modes.

Entrée de MessageDisplay

En plus des champs d'entrée communs, les hooks MessageDisplay reçoivent des identifiants pour le tour et le message, la position de cet appel dans le message, et le nouveau texte dans delta. Les limites des lots dépendent de la manière dont le texte est diffusé ; utilisez donc index et final pour suivre la progression dans un message plutôt que de vous attendre à ce que les lignes soient regroupées d'une manière particulière.

Champ Description
turn_id UUID du tour en cours
message_id UUID du message de l'assistant en cours d'affichage. Stable sur tous les lots d'un même message. Il ne s'agit pas de l'identifiant msg_… de l'API, il ne peut donc pas être mis en correspondance avec les identifiants de message de la transcription
index Index à partir de zéro de ce lot dans le message
final true sur le dernier lot du message. Chaque message a exactement un lot final
delta Les lignes nouvellement terminées depuis le lot précédent, retours à la ligne de fin inclus. Toujours des lignes entières, sauf pour le lot final qui peut se terminer en milieu de ligne. Dans les exécutions interactives, le delta du lot final est vide lorsque le message se termine par un retour à la ligne ; considérez donc final, et non un delta non vide, comme le signal de fin de message. Dans les exécutions Agent SDK et claude -p, l'appel unique contient l'intégralité du message
{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../transcript.jsonl",
  "cwd": "/Users/my-project",
  "hook_event_name": "MessageDisplay",
  "turn_id": "0c9e6a2f-7d41-4f4e-9a15-3f4f7c2b8d10",
  "message_id": "5b2a9c8e-1f63-4d8a-b7c4-9e0d2a6f1c3b",
  "index": 0,
  "final": false,
  "delta": "Here is the plan:\n"
}

Sortie de MessageDisplay

En plus des champs de sortie JSON disponibles pour tous les hooks, les hooks MessageDisplay peuvent renvoyer displayContent pour remplacer le delta à l'écran :

Champ Description
displayContent Texte affiché à la place du delta. Omettez-le pour afficher l'original

Les hooks MessageDisplay n'ont pas de contrôle de décision. Ils ne peuvent pas bloquer le message ni modifier ce qui est stocké dans la transcription ou envoyé à Claude. Claude Code tient compte de displayContent dans leur sortie JSON et ignore systemMessage et continue.

Cet exemple supprime la mise en forme markdown des réponses de Claude pour un affichage en texte brut. Le script lit chaque lot depuis stdin, supprime les marqueurs de gras et les backticks de code en ligne de delta, et renvoie le résultat en tant que displayContent.

Enregistrez un hook de commande pour l'événement dans votre fichier de paramètres :

{
"hooks": {
"MessageDisplay": [
{
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/plain-display.sh",
"args": []
}
]
}
]
}
}

Enregistrez ce script dans .claude/hooks/plain-display.sh dans votre projet et rendez-le exécutable avec chmod +x :

#!/bin/bash
jq '{hookSpecificOutput: {hookEventName: "MessageDisplay", displayContent: (.delta | gsub("\\*\\*"; "") | gsub("`"; ""))}}'

Les lots sans markdown passent sans modification. Si le script échoue, par exemple parce que jq est absent, Claude Code affiche le texte d'origine et ne signale l'échec que dans la sortie de débogage, pas dans la session.

PreToolUse

S'exécute après que Claude a créé les paramètres de l'outil et avant le traitement de l'appel d'outil. Correspond à n'importe quel nom d'outil sauf EndConversation : les outils intégrés tels que Bash, PowerShell, Edit, Write, Read, Glob, Grep, Agent, Workflow, WebFetch, WebSearch, AskUserQuestion et ExitPlanMode, ainsi que tout nom d'outil MCP.

Pour exécuter un hook lorsqu'un fichier spécifique change sur le disque, quel que soit ce qui l'a écrit, utilisez FileChanged plutôt que de faire correspondre les outils d'édition de fichiers par leur nom. Contrairement à PreToolUse, Claude Code exécute les hooks FileChanged après la modification, et ils n'ont pas de contrôle de décision ; ils ne peuvent donc pas bloquer l'écriture.

Utilisez le contrôle de décision de PreToolUse pour autoriser, refuser, demander ou différer l'appel d'outil.

Un hook callback de l'Agent SDK sur PreToolUse qui dépasse son délai d'expiration bloque l'appel d'outil, et Claude reçoit un résultat d'erreur indiquant le délai d'expiration. Un refus explicite renvoyé par un autre hook reste prioritaire.

Entrée de PreToolUse

En plus des champs d'entrée communs, les hooks PreToolUse reçoivent tool_name, tool_input et tool_use_id.

Pour un outil MCP, l'entrée contient également mcp_server, un objet comportant le name du serveur et une source qui indique d'où provient la définition du serveur. Les valeurs de source incluent plugin, sdk et des portées de configuration telles que user et project. McpServerProvenance dans la référence de l'Agent SDK les liste toutes et indique comment traiter une valeur que vous ne reconnaissez pas. Fondez vos décisions de confiance sur source plutôt que sur name ou sur le préfixe de nom d'outil mcp__<server>__. Le champ mcp_server nécessite Claude Code v2.1.274 ou ultérieure.

Pour les outils de fichiers Write, Edit et Read, tool_input.file_path est toujours absolu :

  • Claude Code développe ~ et les chemins relatifs avant l'exécution des hooks, de sorte qu'un hook qui filtre sur les chemins ne peut pas être contourné via ~ ou une écriture relative du même chemin
  • Sous Windows, le chemin arrive avec des barres obliques inverses comme séparateurs, même lorsque votre hook s'exécute sous Git Bash où $PWD ressemble à /c/project
  • Une comparaison écrite avec des barres obliques, comme une vérification de /src/, ne correspond jamais à un chemin avec barres obliques inverses, et l'appel d'outil se poursuit comme si le hook n'avait rien à bloquer
  • Normalisez les séparateurs avant de comparer : FILE_PATH="${FILE_PATH//\\//}" en Bash, ou file_path.replace("\\", "/") en Python, puis faites correspondre un segment de chemin tel que /src/ plutôt que d'ancrer avec ^, puisque le chemin est absolu

Un appel Write sous Windows transmet :

{
  "hook_event_name": "PreToolUse",
  "tool_name": "Write",
  "tool_input": {
    "file_path": "C:\\project\\src\\index.ts",
    "content": "..."
  },
  ...
}

Les champs de tool_input dépendent de l'outil :

Bash

Exécute des commandes shell.

Champ Type Exemple Description
command string "npm test" La commande shell à exécuter
description string "Run test suite" Description facultative de ce que fait la commande
timeout number 120000 Délai d'expiration facultatif en millisecondes. Les valeurs supérieures au maximum sont ramenées au maximum plutôt que rejetées
run_in_background boolean false Indique s'il faut exécuter la commande en arrière-plan

Lorsqu'une commande Bash modifie des fichiers dans un dépôt Git, Claude Code peut enregistrer ce qui a changé. Il enregistre les modifications dans tous les modes de permission lorsque le paramètre bashEditDiffEnabled active l'enregistrement ; l'entrée de ce paramètre indique quels fichiers peuvent le définir. Sinon, il ne les enregistre qu'en mode auto et en mode bypassPermissions, et uniquement lorsque Claude Code demande à Claude de modifier des fichiers via Bash. Définissez bashEditDiffEnabled sur false pour désactiver l'enregistrement. Les commandes en arrière-plan et les commandes en lecture seule ne comportent pas de diff.

Votre hook PostToolUse reçoit alors les fichiers modifiés dans tool_response.bashEditDiff. La liste couvre ce qui a changé dans le dépôt pendant l'exécution de la commande. Les fichiers ignorés par Git et les fichiers des sous-modules ne sont pas listés. Nécessite Claude Code v2.1.269 ou ultérieure.

changedFiles et files listent ce que la commande a modifié ; les autres champs indiquent dans quelle mesure cette liste est complète et fiable.

Champ Type Exemple Description
changedFiles array ["/path/to/src/app.ts"] Chemins absolus des fichiers modifiés par la commande, 200 au maximum. Présent dès que files contient un diff ou que moreFiles est supérieur à zéro
files array [{"filePath": "/path/to/src/app.ts", "hunks": [...]}] Diffs d'au plus 5 fichiers modifiés, pour l'affichage. created ou deleted vaut true pour un fichier que la commande a ajouté ou supprimé
moreFiles number 2 Nombre de fichiers modifiés sans diff dans files
unavailable boolean true Défini lorsque le diff est incomplet ou n'a pas pu être obtenu
skipped boolean true Défini pour une commande Git qui déplace l'arbre de travail, comme git checkout ou git stash, de sorte que Claude Code ne calcule pas de diff
shared boolean true Défini lorsqu'un autre appel d'outil Bash, comme celui d'un sous-agent, s'est exécuté dans le même dépôt au même moment, de sorte que certaines modifications listées peuvent provenir de cette commande
PowerShell

Exécute des commandes PowerShell. Consultez l'outil PowerShell pour la disponibilité par plateforme.

Les champs sont identiques à ceux de l'outil Bash, avec la chaîne de commande dans command :

Champ Type Exemple Description
command string "Get-ChildItem -Recurse" La commande PowerShell à exécuter
description string "List files recursively" Description facultative de ce que fait la commande
timeout number 120000 Délai d'expiration facultatif en millisecondes
run_in_background boolean false Indique s'il faut exécuter la commande en arrière-plan

Utilisez le matcher Bash|PowerShell dans les hooks qui inspectent les commandes shell, afin qu'ils couvrent les deux outils :

  • Sous Windows, partout où l'outil PowerShell est activé, Claude traite PowerShell comme le shell principal et y fait passer les commandes shell.
  • Sous Windows sans Git Bash, l'outil est activé automatiquement et Claude Code n'enregistre pas du tout l'outil Bash.
  • Un hook qui ne correspond qu'à Bash ne s'y déclenche jamais.
Write

Crée ou écrase un fichier.

Champ Type Exemple Description
file_path string "/path/to/file.txt" Chemin absolu du fichier à écrire
content string "file content" Contenu à écrire dans le fichier
Edit

Remplace une chaîne dans un fichier existant.

Champ Type Exemple Description
file_path string "/path/to/file.txt" Chemin absolu du fichier à modifier
old_string string "original text" Texte à rechercher et remplacer
new_string string "replacement text" Texte de remplacement
replace_all boolean false Indique s'il faut remplacer toutes les occurrences
Read

Lit le contenu d'un fichier.

Champ Type Exemple Description
file_path string "/path/to/file.txt" Chemin absolu du fichier à lire
offset number 10 Numéro de ligne facultatif à partir duquel commencer la lecture
limit number 50 Nombre facultatif de lignes à lire
Glob

Trouve les fichiers correspondant à un motif glob.

Champ Type Exemple Description
pattern string "**/*.ts" Motif glob auquel faire correspondre les fichiers
path string "/path/to/dir" Répertoire facultatif dans lequel rechercher. Par défaut, le répertoire de travail actuel
Grep

Recherche dans le contenu des fichiers avec des expressions régulières.

Champ Type Exemple Description
pattern string "TODO.*fix" Motif d'expression régulière à rechercher
path string "/path/to/dir" Fichier ou répertoire facultatif dans lequel rechercher
glob string "*.ts" Motif glob facultatif pour filtrer les fichiers
output_mode string "content" "content", "files_with_matches" ou "count". Par défaut "files_with_matches"
-i boolean true Recherche insensible à la casse
multiline boolean false Active la correspondance multiligne
WebFetch

Récupère et traite du contenu web.

Champ Type Exemple Description
url string "https://example.com/api" URL dont récupérer le contenu
prompt string "Extract the API endpoints" Prompt à exécuter sur le contenu récupéré
WebSearch

Effectue une recherche sur le web.

Champ Type Exemple Description
query string "react hooks best practices" Requête de recherche
allowed_domains array ["docs.example.com"] Facultatif : inclure uniquement les résultats de ces domaines
blocked_domains array ["spam.example.com"] Facultatif : exclure les résultats de ces domaines
Agent

Lance un sous-agent.

Champ Type Exemple Description
prompt string "Find all API endpoints" La tâche que l'agent doit effectuer
description string "Find API endpoints" Brève description de la tâche
subagent_type string "Explore" Type d'agent spécialisé à utiliser
model string "sonnet" Alias de modèle facultatif pour remplacer le modèle par défaut

Lorsqu'un appel Agent au premier plan se termine, votre hook PostToolUse reçoit le résultat du sous-agent et la télémétrie de l'exécution dans tool_response. Lisez ces champs pour inspecter l'exécution ; pour des agrégats de tokens et de coûts sur l'ensemble des sous-agents, utilisez les compteurs de tokens et de coûts filtrés sur query_source "subagent", car totalTokens et usage ne couvrent que la requête finale :

Champ Type Exemple Description
status string "completed" "completed" pour les sous-agents au premier plan, "async_launched" pour les sous-agents en arrière-plan. Les sous-agents s'exécutent en arrière-plan par défaut, donc un appel Agent qui omet run_in_background produit également "async_launched"
agentId string "a4d2c8f1e0b3a297" Identifiant de l'exécution du sous-agent
content array [{"type": "text", "text": "Found 12 endpoints..."}] Les blocs de texte finaux du sous-agent ou, pour un sous-agent dont le rapport passe par SubagentHandback, une brève note sur cette remise à leur place
resolvedModel string "claude-sonnet-4-5" Modèle sur lequel le sous-agent a démarré, qui peut différer du modèle demandé
modelsUsed array ["claude-sonnet-4-5", "claude-haiku-4-5"] Modèles utilisés dans l'ordre, les répétitions consécutives étant fusionnées ; défini uniquement lorsque le modèle a été changé en cours d'exécution. Nécessite Claude Code v2.1.212 ou ultérieure
totalTokens number 12450 Nombre de tokens de la requête API finale du sous-agent : tokens d'entrée, de sortie et de cache combinés. Il ne s'agit pas d'un total sur l'ensemble de l'exécution
totalDurationMs number 48211 Durée réelle de l'exécution du sous-agent
totalToolUseCount number 7 Nombre d'appels d'outils effectués par le sous-agent
usage object {"input_tokens": 8320, ...} Répartition des tokens par type pour la requête API finale : input_tokens, output_tokens, cache_creation_input_tokens, cache_read_input_tokens

Avec Claude Code v2.1.271 ou ultérieure, un sous-agent qui s'exécute avec l'outil SubagentHandback, que Claude Code fournit en mode auto, transmet son rapport via cet outil plutôt que de le renvoyer sous forme de texte. Le champ content de son résultat completed contient alors une brève note sur cette remise plutôt que le rapport lui-même. Pour lire le rapport, faites correspondre un hook PreToolUse ou PostToolUse à SubagentHandback et lisez tool_input.message.

Pour les sous-agents en arrière-plan, l'outil rend la main lorsque la tâche passe en arrière-plan, donc tool_response ne contient aucun champ d'utilisation : un lancement en arrière-plan rend la main immédiatement, et une tâche au premier plan que Claude Code passe en arrière-plan en cours d'exécution rend la main lors de cette transition. Elle contient status: "async_launched", agentId, description, prompt, outputFile et resolvedModel.

Dans une réponse completed, resolvedModel indique le modèle sur lequel le sous-agent a démarré, qui peut différer de la valeur model dans tool_input, par exemple lorsque availableModels ou un autre remplacement s'applique. Dans une réponse async_launched, resolvedModel indique le modèle utilisé au moment où l'agent est passé en arrière-plan, de sorte qu'un changement survenu avant le passage en arrière-plan y est reflété. modelsUsed et le comportement de resolvedModel au moment du passage en arrière-plan nécessitent Claude Code v2.1.212 ou ultérieure.

AskUserQuestion

Pose à l'utilisateur une à quatre questions à choix multiples.

Champ Type Exemple Description
questions array [{"question": "Which framework?", "header": "Framework", "options": [{"label": "React"}], "multiSelect": false}] Questions à présenter, chacune avec une chaîne question, un header court, un tableau options et un flag multiSelect facultatif
answers object {"Which framework?": "React"} Facultatif. Associe le texte de la question au libellé de l'option sélectionnée. Les réponses à sélection multiple joignent les libellés par des virgules. Claude ne définit pas ce champ ; fournissez-le via updatedInput pour répondre par programmation
ExitPlanMode

Présente un plan et demande à l'utilisateur de l'approuver avant que Claude ne quitte le mode plan. Claude écrit le plan dans un fichier sur le disque avant d'appeler l'outil, donc le tool_input littéral fourni par le modèle est généralement vide. Claude Code injecte le contenu du plan et le chemin du fichier avant de transmettre l'entrée aux hooks.

Champ Type Exemple Description
plan string "## Refactor auth\n1. Extract..." Contenu du plan en Markdown. Injecté depuis le fichier de plan sur le disque
planFilePath string "/Users/.../plans/refactor-auth.md" Chemin du fichier de plan. Injecté
allowedPrompts array [{"tool": "Bash", "prompt": "run tests"}] Déprécié. Claude Code accepte le champ mais l'ignore. Avant la v2.1.205, il contenait les permissions basées sur des prompts que Claude demandait pour mettre en œuvre le plan

Dans PostToolUse, tool_response est un objet avec des champs plan et filePath contenant le plan approuvé, ainsi que des indicateurs d'état internes. Lisez tool_response.plan pour obtenir le contenu du plan plutôt que de relire le fichier sur le disque.

Contrôle de décision de PreToolUse

Les hooks PreToolUse peuvent contrôler si un appel d'outil se poursuit. Contrairement aux autres hooks qui utilisent un champ decision de premier niveau, PreToolUse renvoie sa décision dans un objet hookSpecificOutput. Cela lui donne un contrôle plus riche : quatre issues possibles (autoriser, refuser, demander ou différer) ainsi que la possibilité de modifier l'entrée de l'outil avant l'exécution.

Champ Description
permissionDecision "allow" ignore la demande de permission, sauf pour les actions qu'aucun mode n'approuve automatiquement et pour AskUserQuestion et ExitPlanMode, qui nécessitent updatedInput en complément. "deny" empêche l'appel d'outil. "ask" demande à l'utilisateur de confirmer. "defer" se termine proprement afin que l'outil puisse être repris plus tard. Les règles de refus et de demande sont toujours évaluées, quelle que soit la réponse du hook
permissionDecisionReason Pour "ask", affiché à l'utilisateur dans la demande de permission. Lorsque Claude Code refuse l'appel dans une exécution -p où personne ne peut répondre à cette demande, Claude lit la raison dans le résultat de l'outil à la place. Pour "deny", affiché à Claude. Pour "allow" et "defer", écrit uniquement dans le log de débogage
updatedInput Modifie les paramètres d'entrée de l'outil avant l'exécution. Remplace l'intégralité de l'objet d'entrée ; incluez donc les champs inchangés en plus des champs modifiés. Claude Code évalue les règles de permission et l'éligibilité au passage automatique en arrière-plan d'une commande Bash par rapport à l'entrée renvoyée par votre hook, et non à l'entrée envoyée par Claude. Combinez avec "allow" pour approuver automatiquement, ou avec "ask" pour montrer l'entrée modifiée à l'utilisateur. Ignoré pour "defer"
additionalContext Chaîne ajoutée au contexte de Claude en plus du résultat de l'outil. Ignorée lorsque permissionDecision vaut "defer". Consultez Ajouter du contexte pour Claude

Lorsque plusieurs hooks PreToolUse renvoient des décisions différentes, l'ordre de priorité est deny > defer > ask > allow.

Un hook qui bloque en se terminant avec le code 2 est traité de la même manière que "deny" : Claude voit le message stderr comme raison du refus.

Lorsqu'un hook renvoie "ask", la demande de permission affichée à l'utilisateur comporte une étiquette identifiant l'origine du hook : [settings] pour un hook provenant d'un fichier de paramètres ou du frontmatter d'un agent, [plugin:<name>] pour le hook d'un plugin, ou [skill] pour un hook provenant du frontmatter d'un skill. Cela aide les utilisateurs à comprendre quelle source de configuration demande une confirmation.

Un "ask" renvoyé par un hook force également une demande de permission en mode auto : le classifieur peut toujours refuser l'appel d'outil, mais il ne peut pas l'approuver silencieusement. Avant la v2.1.211, le classifieur pouvait approuver une commande Bash s'exécutant hors du sandbox sans afficher la demande réclamée par le hook ; le classifieur appliquait néanmoins ses propres règles de sécurité à cette commande, et un "deny" de hook était toujours respecté.

{
  "hookSpecificOutput": {
    "hookEventName": "PreToolUse",
    "permissionDecision": "allow",
    "permissionDecisionReason": "My reason here",
    "updatedInput": {
      "field_to_modify": "new value"
    },
    "additionalContext": "Current environment: production. Proceed with caution."
  }
}

En mode non interactif avec le flag -p, Claude Code ne propose AskUserQuestion et ExitPlanMode que lorsque l'exécution dispose d'un hôte de permissions pour recevoir la demande, comme un callback canUseTool de l'Agent SDK. Ces outils nécessitent une interaction de l'utilisateur. Renvoyer permissionDecision: "allow" avec updatedInput satisfait cette exigence : le hook lit l'entrée de l'outil depuis stdin, recueille la réponse via votre propre interface et la renvoie dans updatedInput afin que l'outil s'exécute sans demande. Renvoyer "allow" seul ne suffit pas pour ces outils. Pour AskUserQuestion, renvoyez le tableau questions d'origine et ajoutez un objet answers associant le texte de chaque question à la réponse choisie.

Un outil MCP que son serveur marque avec _meta["anthropic/requiresUserInteraction"] est plus strict : un hook ne peut pas ignorer sa demande d'approbation avec "allow", avec ou sans updatedInput, car Claude Code ne peut pas confirmer que le hook a recueilli l'interaction dont l'outil a besoin.

Différer un appel d'outil

"defer" est destiné aux intégrations qui exécutent claude -p comme sous-processus et lisent sa sortie JSON, comme une application Agent SDK ou une interface personnalisée construite sur Claude Code. Il permet à ce processus appelant de mettre Claude en pause sur un appel d'outil, de recueillir une saisie via sa propre interface, puis de reprendre là où il s'était arrêté. Claude Code ne respecte cette valeur qu'en mode non interactif avec le flag -p. Dans les sessions interactives, il consigne un avertissement et ignore le résultat du hook.

L'outil AskUserQuestion est le cas typique : Claude veut poser une question à l'utilisateur, mais il n'y a pas de terminal pour y répondre. Une exécution -p ne propose AskUserQuestion que lorsqu'elle dispose d'un hôte de permissions, comme un outil MCP que vous transmettez avec --permission-prompt-tool ; démarrez donc l'exécution avec l'un d'eux. L'aller-retour fonctionne ainsi :

  1. Claude appelle AskUserQuestion. Le hook PreToolUse se déclenche.
  2. Le hook renvoie permissionDecision: "defer". L'outil ne s'exécute pas. Le processus se termine avec stop_reason: "tool_deferred" et l'appel d'outil en attente est conservé dans la transcription.
  3. Le processus appelant lit deferred_tool_use dans le résultat du SDK, affiche la question dans sa propre interface et attend une réponse.
  4. Le processus appelant exécute claude -p --resume <session-id> avec le même hôte de permissions. Le même appel d'outil déclenche à nouveau PreToolUse.
  5. Le hook renvoie permissionDecision: "allow" avec la réponse dans updatedInput. L'outil s'exécute et Claude continue.

Le champ deferred_tool_use contient l'id, le name et l'input de l'outil. L'input correspond aux paramètres que Claude a générés pour l'appel d'outil, capturés avant l'exécution :

{
  "type": "result",
  "subtype": "success",
  "stop_reason": "tool_deferred",
  "session_id": "abc123",
  "deferred_tool_use": {
    "id": "toolu_01abc",
    "name": "AskUserQuestion",
    "input": { "questions": [{ "question": "Which framework?", "header": "Framework", "options": [{"label": "React"}, {"label": "Vue"}], "multiSelect": false }] }
  }
}

Il n'y a ni délai d'expiration ni limite de nouvelles tentatives. La session reste sur le disque jusqu'à ce que vous la repreniez, sous réserve du nettoyage de rétention cleanupPeriodDays, qui supprime les fichiers de session après 30 jours par défaut, conformément aux règles du nettoyage de rétention. Si la réponse n'est pas prête lorsque vous reprenez, le hook peut renvoyer "defer" à nouveau et le processus se termine de la même manière. Le processus appelant décide quand sortir de la boucle en renvoyant finalement "allow" ou "deny" depuis le hook.

"defer" ne fonctionne que lorsque Claude effectue un seul appel d'outil dans le tour. Si Claude effectue plusieurs appels d'outils à la fois, "defer" est ignoré avec un avertissement et l'outil suit le flux de permission normal. Cette contrainte existe parce que la reprise ne peut réexécuter qu'un seul outil : il n'y a aucun moyen de différer un appel d'un lot sans laisser les autres non résolus.

Si l'outil différé n'est plus disponible lorsque vous reprenez, le processus se termine avec stop_reason: "tool_deferred_unavailable" et is_error: true avant que le hook ne se déclenche. Cela se produit lorsqu'un serveur MCP qui fournissait l'outil n'est pas connecté pour la session reprise. Le payload deferred_tool_use est tout de même inclus afin que vous puissiez identifier l'outil manquant.

PermissionRequest

S'exécute lorsque Claude Code est sur le point de vous demander la permission d'utiliser un outil. Dans les sessions qui ne peuvent pas afficher de demande, comme les sous-agents en arrière-plan en mode non interactif, Claude Code exécute tout de même ces hooks, et si aucun hook ne renvoie de décision, il refuse l'appel d'outil. Pour un appel qui atteint un --permission-prompt-tool ou le callback canUseTool de l'Agent SDK, les hooks s'exécutent en parallèle de votre hôte, et la première décision prise s'applique. Utilisez le contrôle de décision de PermissionRequest pour autoriser ou refuser au nom de l'utilisateur.

Utilisez cet événement lorsque vous avez besoin d'un signal au moment où Claude demande la permission d'utiliser un outil. Claude Code n'exécute un hook Notification de type permission_prompt qu'après que la demande a attendu environ six secondes.

Claude Code n'exécute pas les hooks PermissionRequest pour la requête réseau d'une commande exécutée dans le sandbox. Pour obtenir un signal pour cette demande, utilisez le type de notification permission_prompt.

Correspond au nom de l'outil, avec les mêmes valeurs que PreToolUse.

Entrée de PermissionRequest

Les hooks PermissionRequest reçoivent les champs tool_name et tool_input comme les hooks PreToolUse, mais sans tool_use_id. Pour un outil MCP, ils reçoivent également l'objet mcp_server. Un tableau facultatif permission_suggestions contient les mises à jour de permissions que Claude Code suggère pour cette demande, comme l'ajout d'une règle d'autorisation ou le changement du mode de permission.

Le tableau permission_suggestions n'est pas une liste exacte des options que vous voyez, car chaque boîte de dialogue de permission construit ses propres options. Certaines boîtes de dialogue, comme celle des modifications de fichiers, ne lisent pas du tout le tableau et dérivent leurs options de la demande elle-même. Une boîte de dialogue qui le lit peut tout de même masquer une option dont la suggestion reste dans le tableau, par exemple lorsque allowManagedPermissionRulesOnly masque les options d'enregistrement de règles. Elle peut également proposer des options sans entrée de suggestion, comme Yes, and switch to auto mode, qui change directement le mode de permission plutôt que de passer par une mise à jour de permissions.

Les hooks PreToolUse s'exécutent avant chaque appel d'outil, qu'il nécessite une permission ou non. Les hooks PermissionRequest ne s'exécutent que lorsque Claude Code est sur le point de vous demander la permission, ou lorsqu'il refuserait autrement automatiquement un appel qui ne peut pas afficher de demande. Aucun des deux événements ne se déclenche pour EndConversation.

{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "cwd": "/Users/...",
  "permission_mode": "default",
  "hook_event_name": "PermissionRequest",
  "tool_name": "Bash",
  "tool_input": {
    "command": "rm -rf node_modules",
    "description": "Remove node_modules directory"
  },
  "permission_suggestions": [
    {
      "type": "addRules",
      "rules": [{ "toolName": "Bash", "ruleContent": "rm -rf node_modules" }],
      "behavior": "allow",
      "destination": "localSettings"
    }
  ]
}

Contrôle de décision de PermissionRequest

Les hooks PermissionRequest peuvent autoriser ou refuser des demandes de permission. En plus des champs de sortie JSON disponibles pour tous les hooks, votre script de hook peut renvoyer un objet decision avec ces champs propres à l'événement :

Champ Description
behavior "allow" accorde la permission, "deny" la refuse. Les règles de refus et de demande sont toujours évaluées, donc un hook renvoyant "allow" ne remplace pas une règle de refus correspondante
updatedInput Pour "allow" uniquement : modifie les paramètres d'entrée de l'outil avant l'exécution. Remplace l'intégralité de l'objet d'entrée ; incluez donc les champs inchangés en plus des champs modifiés. L'entrée modifiée est réévaluée par rapport aux règles de refus et de demande
updatedPermissions Pour "allow" uniquement : tableau d'entrées de mise à jour de permissions à appliquer, comme l'ajout d'une règle d'autorisation ou le changement du mode de permission de la session
message Pour "deny" uniquement : indique à Claude pourquoi la permission a été refusée
interrupt Pour "deny" uniquement : si true, arrête Claude

Un hook qui se termine avec le code 2 sans objet decision laisse le flux de permission inchangé, et sa sortie stderr est ignorée. Seul l'objet decision peut accorder ou refuser la demande.

{
  "hookSpecificOutput": {
    "hookEventName": "PermissionRequest",
    "decision": {
      "behavior": "allow",
      "updatedInput": {
        "command": "npm run lint"
      }
    }
  }
}

Entrées de mise à jour de permissions

Le champ de sortie updatedPermissions et le champ d'entrée permission_suggestions utilisent tous deux le même tableau d'objets d'entrée. Chaque entrée possède un type qui détermine ses autres champs, et une destination qui contrôle où la modification est écrite.

type Champs Effet
addRules rules, behavior, destination Ajoute des règles de permission. rules est un tableau d'objets {toolName, ruleContent?}. Omettez ruleContent pour faire correspondre l'outil entier. behavior vaut "allow", "deny" ou "ask"
replaceRules rules, behavior, destination Remplace toutes les règles du behavior donné à la destination par les rules fournies
removeRules rules, behavior, destination Supprime les règles correspondantes du behavior donné
setMode mode, destination Change le mode de permission. Les modes valides sont default, auto, acceptEdits, dontAsk, bypassPermissions, plan et manual comme alias de default. L'alias manual nécessite Claude Code v2.1.200 ou ultérieure
addDirectories directories, destination Ajoute des répertoires de travail. directories est un tableau de chaînes de chemins
removeDirectories directories, destination Supprime des répertoires de travail

Le champ destination de chaque entrée détermine si la modification reste en mémoire ou est conservée dans un fichier de paramètres.

destination Écrit dans
session en mémoire uniquement, supprimé à la fin de la session
localSettings .claude/settings.local.json
projectSettings .claude/settings.json
userSettings ~/.claude/settings.json

Un hook peut renvoyer l'une des permission_suggestions qu'il a reçues comme sa propre sortie updatedPermissions.

PostToolUse

S'exécute immédiatement après qu'un outil s'est terminé avec succès.

Correspond au nom de l'outil, avec les mêmes valeurs que PreToolUse.

Élargissez la correspondance lorsque le nom de l'outil n'est pas le bon filtre :

  • Pour exécuter un hook après la réussite de n'importe quel outil, omettez le matcher ou définissez-le sur "*". Votre hook peut alors découvrir lui-même ce qui a changé, par exemple en exécutant git status --porcelain, qui liste aussi les fichiers non suivis que git diff ignore. Pour les appels d'outils qui échouent, ajoutez le même hook sous PostToolUseFailure.
  • Pour exécuter un hook lorsqu'un fichier spécifique change sur le disque, quel que soit ce qui l'a écrit, utilisez FileChanged. Claude Code n'exécute pas un hook PostToolUse correspondant à Edit|Write lorsqu'une commande Bash ou un processus extérieur à Claude Code réécrit le même fichier.

Entrée PostToolUse

Les hooks PostToolUse se déclenchent après qu'un outil s'est déjà exécuté avec succès. L'entrée inclut à la fois tool_input, les arguments envoyés à l'outil, et tool_response, le résultat qu'il a renvoyé. Le schéma exact de ces deux champs dépend de l'outil. Les chemins tool_input des outils de fichiers arrivent dans le même format que pour PreToolUse : toujours absolus, avec les séparateurs natifs de la plateforme, donc des barres obliques inverses sous Windows. Pour un outil MCP, l'entrée contient également l'objet mcp_server.

{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "cwd": "/Users/...",
  "permission_mode": "default",
  "hook_event_name": "PostToolUse",
  "tool_name": "Write",
  "tool_input": {
    "file_path": "/path/to/file.txt",
    "content": "file content"
  },
  "tool_response": {
    "filePath": "/path/to/file.txt",
    "type": "create"
  },
  "tool_use_id": "toolu_01ABC123...",
  "duration_ms": 12
}
Champ Description
duration_ms Facultatif. Durée d'exécution de l'outil en millisecondes. Exclut le temps passé dans les demandes de permission et les hooks PreToolUse

Contrôle de décision PostToolUse

Les hooks PostToolUse peuvent fournir un retour à Claude après l'exécution de l'outil. En plus des champs de sortie JSON disponibles pour tous les hooks, votre script de hook peut renvoyer ces champs propres à l'événement :

Champ Description
decision "block" ajoute la reason à côté du résultat de l'outil. Claude voit toujours la sortie d'origine ; pour la remplacer, utilisez updatedToolOutput
reason Explication affichée à Claude lorsque decision vaut "block"
additionalContext Chaîne ajoutée au contexte de Claude avec le résultat de l'outil. Voir Ajouter du contexte pour Claude
classifierContext Courte note sur le résultat de cet appel destinée au classifieur du mode auto plutôt qu'à Claude. Voir Annoter un résultat pour le classifieur du mode auto. Nécessite Claude Code v2.1.236 ou ultérieur
updatedToolOutput Remplace la sortie de l'outil par la valeur fournie avant qu'elle ne soit envoyée à Claude. La valeur doit correspondre à la forme de sortie de l'outil
updatedMCPToolOutput Remplace la sortie des outils MCP uniquement. Préférez updatedToolOutput, qui fonctionne pour tous les outils

L'exemple ci-dessous remplace la sortie d'un appel Bash. La valeur de remplacement correspond à la forme de sortie de l'outil Bash :

{
  "hookSpecificOutput": {
    "hookEventName": "PostToolUse",
    "additionalContext": "Additional information for Claude",
    "updatedToolOutput": {
      "stdout": "[redacted]",
      "stderr": "",
      "interrupted": false,
      "isImage": false
    }
  }
}

Annoter un résultat pour le classifieur du mode auto

Renvoyez classifierContext pour envoyer une courte note sur le résultat de l'appel d'outil au classifieur du mode auto plutôt qu'à Claude. Le classifieur ne reçoit jamais les résultats des outils eux-mêmes, ce champ est donc le moyen prévu pour lui indiquer quelque chose sur ce qu'un appel a renvoyé avant qu'il n'examine les actions suivantes. Ce champ nécessite Claude Code v2.1.236 ou ultérieur.

L'exemple ci-dessous indique au classifieur d'où provient la sortie d'une requête :

{
  "hookSpecificOutput": {
    "hookEventName": "PostToolUse",
    "classifierContext": "This query ran against the staging database, not production."
  }
}

Le poids que le classifieur accorde à la note dépend de l'endroit où vous avez configuré le hook :

  • Hooks configurés dans Claude Code : pour les hooks provenant des fichiers de paramètres, des plugins, des skills et du frontmatter des agents, le classifieur traite la note comme un contexte non vérifié fourni par l'application. La note n'établit jamais l'intention de l'utilisateur, et si elle affirme que vous avez approuvé ou demandé quelque chose, le classifieur vérifie cette affirmation par rapport à vos propres messages dans la conversation
  • Callbacks Agent SDK en processus : lorsqu'une application intégrant Claude Code enregistre le hook comme callback du SDK TypeScript et renvoie la note pendant la session en cours, le classifieur peut considérer une déclaration de l'utilisateur relayée dans la note comme une intention de l'utilisateur. Une telle déclaration peut satisfaire une exigence de consentement que le classifieur accepterait d'un message que vous envoyez, mais elle ne lève jamais un blocage que votre propre message ne pourrait pas lever non plus. Après la reprise d'une session, Claude Code traite les notes restaurées comme un contexte non vérifié. Lorsque des hooks des deux groupes annotent le même appel, le classifieur traite la note combinée comme non vérifiée

Claude Code applique ces limites lors de la transmission de la note :

  • Longueur : Claude Code plafonne les notes d'un appel d'outil à 2 000 caractères et tronque le reste. Ce plafond est partagé entre tous les hooks qui répondent à cet appel
  • Réponses synchrones uniquement : Claude Code ignore le champ dans la réponse d'un hook qui s'exécute en arrière-plan, car cette réponse arrive après que Claude Code a enregistré le résultat de l'outil
  • Appels que le classifieur n'enregistre pas : la transcription du classifieur omet les consultations en lecture seule comme les lectures de fichiers et les recherches. Claude Code supprime une note attachée à l'un de ces appels
  • Interaction avec les réécritures : lorsque la note décrit une sortie que vous remplacez avec updatedToolOutput, renvoyez les deux champs dans la même réponse de hook. Claude Code abandonne la note si cette réécriture est rejetée ou si la réécriture d'un autre hook la remplace. Claude Code transmet une note que vous renvoyez sans réécriture même lorsqu'un autre hook réécrit la sortie

PostToolUseFailure

S'exécute lorsqu'un outil dont l'exécution a commencé échoue : l'outil a levé une erreur, ou un outil MCP a renvoyé un résultat d'erreur. Utilisez-le pour journaliser les échecs, envoyer des alertes ou fournir un retour correctif à Claude.

Correspond au nom de l'outil, avec les mêmes valeurs que PreToolUse.

Entrée PostToolUseFailure

Les hooks PostToolUseFailure reçoivent les mêmes champs tool_name et tool_input que PostToolUse, ainsi que des informations sur l'erreur sous forme de champs de premier niveau. Pour un outil MCP, ils reçoivent également l'objet mcp_server. Par exemple, une commande npm test en échec pourrait transmettre :

{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "cwd": "/Users/...",
  "permission_mode": "default",
  "hook_event_name": "PostToolUseFailure",
  "tool_name": "Bash",
  "tool_input": {
    "command": "npm test",
    "description": "Run test suite"
  },
  "tool_use_id": "toolu_01ABC123...",
  "error": "Exit code 1\nError: Cannot find module 'express'",
  "is_interrupt": false,
  "duration_ms": 4187
}
Champ Description
error Chaîne décrivant ce qui s'est mal passé. Le format dépend de l'outil qui a échoué
is_interrupt Booléen facultatif. Vrai lorsque l'échec est parvenu à Claude Code sous forme d'abandon plutôt que d'erreur signalée par l'outil. L'annulation d'un outil en cours d'exécution ne déclenche pas ce hook ; le résultat de l'outil contient alors le message d'interruption
duration_ms Facultatif. Durée d'exécution de l'outil en millisecondes. Exclut le temps passé dans les demandes de permission et les hooks PreToolUse

La chaîne error est généralement le même texte que Claude reçoit comme résultat de l'outil en échec. Son format varie selon l'outil et le type d'échec. Basez votre hook sur tool_name, is_interrupt et la première ligne Exit code N ; traitez le reste de la chaîne comme du texte d'affichage, et non comme un format stable.

  • Pour Bash et PowerShell, une commande qui s'est exécutée puis terminée produit une première ligne Exit code N, suivie de toute la sortie produite par la commande en un seul bloc, avec stdout et stderr entremêlés
  • Un payload peut aussi contenir un simple message d'échec sans ligne de code de sortie, lorsque Claude Code n'a pas pu démarrer le processus shell lui-même
  • Claude Code tronque le milieu des longues chaînes autour d'un marqueur ... [N characters truncated] ..., et peut insérer ses propres lignes, comme Command timed out after 2m 0s

Contrôle de décision PostToolUseFailure

Les hooks PostToolUseFailure peuvent fournir du contexte à Claude après l'échec d'un outil. En plus des champs de sortie JSON disponibles pour tous les hooks, votre script de hook peut renvoyer ces champs propres à l'événement :

Champ Description
additionalContext Chaîne ajoutée au contexte de Claude avec l'erreur. Voir Ajouter du contexte pour Claude
{
  "hookSpecificOutput": {
    "hookEventName": "PostToolUseFailure",
    "additionalContext": "Additional information about the failure for Claude"
  }
}

PostToolBatch

S'exécute une fois après la résolution de tous les appels d'outils d'un lot, avant que Claude Code n'envoie la requête suivante au modèle. PostToolUse se déclenche une fois par outil, ce qui signifie qu'il se déclenche de manière concurrente lorsque Claude effectue des appels d'outils parallèles. PostToolBatch se déclenche exactement une fois avec le lot complet ; c'est donc le bon endroit pour injecter un contexte qui dépend de l'ensemble des outils exécutés plutôt que d'un seul outil. Il n'y a pas de matcher pour cet événement.

Entrée PostToolBatch

En plus des champs d'entrée communs, les hooks PostToolBatch reçoivent tool_calls, un tableau décrivant chaque appel d'outil du lot :

{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "cwd": "/Users/...",
  "permission_mode": "default",
  "hook_event_name": "PostToolBatch",
  "tool_calls": [
    {
      "tool_name": "Read",
      "tool_input": {"file_path": "/.../ledger/accounts.py"},
      "tool_use_id": "toolu_01...",
      "tool_response": "1\tfrom __future__ import annotations\n2\t..."
    },
    {
      "tool_name": "Read",
      "tool_input": {"file_path": "/.../ledger/transactions.py"},
      "tool_use_id": "toolu_02...",
      "tool_response": "1\tfrom __future__ import annotations\n2\t..."
    }
  ]
}

tool_response contient le même contenu que celui que le modèle reçoit dans le bloc tool_result correspondant. La valeur est une chaîne sérialisée ou un tableau de blocs de contenu, exactement tel que l'outil l'a émis. Pour Read, cela signifie du texte préfixé par des numéros de ligne plutôt que le contenu brut du fichier. Les réponses peuvent être volumineuses ; n'analysez donc que les champs dont vous avez besoin.

Contrôle de décision PostToolBatch

Les hooks PostToolBatch peuvent injecter du contexte pour Claude. En plus des champs de sortie JSON disponibles pour tous les hooks, votre script de hook peut renvoyer ces champs propres à l'événement :

Champ Description
additionalContext Chaîne de contexte injectée une fois avant le prochain appel au modèle. Voir Ajouter du contexte pour Claude pour les détails de transmission, ce qu'il faut y mettre et la façon dont les sessions reprises gèrent les valeurs passées
{
  "hookSpecificOutput": {
    "hookEventName": "PostToolBatch",
    "additionalContext": "These files are part of the ledger module. Run pytest before marking the task complete."
  }
}

Renvoyer decision: "block" ou continue: false arrête la boucle agentique avant le prochain appel au modèle. Le message de blocage provient de la reason ou du stopReason JSON, ou de stderr avec le code de sortie 2. Vous le voyez comme un avertissement dans la transcription, et il reste dans la conversation, de sorte que Claude le voit lorsque la conversation reprend.

PermissionDenied

S'exécute lorsque le mode auto refuse un appel d'outil, y compris lorsqu'il refuse sans verdict du classifieur parce qu'un contrôle de sécurité distinct du mode auto a refusé la requête du classifieur lui-même ou que sa réponse n'a pas pu être analysée. Ce hook ne se déclenche qu'en mode auto : il ne s'exécute pas lorsque vous refusez manuellement une boîte de dialogue de permission, lorsqu'un hook PreToolUse bloque un appel, ou lorsqu'une règle deny correspond. Utilisez-le pour journaliser les refus, ajuster la configuration ou indiquer au modèle qu'il peut réessayer l'appel d'outil.

Correspond au nom de l'outil, avec les mêmes valeurs que PreToolUse.

Entrée PermissionDenied

En plus des champs d'entrée communs, les hooks PermissionDenied reçoivent tool_name, tool_input, tool_use_id et reason. Pour un outil MCP, ils reçoivent également l'objet mcp_server.

{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "cwd": "/Users/...",
  "permission_mode": "auto",
  "hook_event_name": "PermissionDenied",
  "tool_name": "Bash",
  "tool_input": {
    "command": "rm -rf /tmp/build",
    "description": "Clean build directory"
  },
  "tool_use_id": "toolu_01ABC123...",
  "reason": "[Irreversible Local Destruction]"
}
Champ Description
reason Le motif du refus. Pour un verdict du classifieur, dans la plupart des sessions, il nomme la règle correspondante entre crochets, comme [Data Exfiltration] ; voir Examiner les refus pour les autres formes. Pour un refus sans verdict, il commence par Auto mode could not evaluate this action and is blocking it for safety. Pour un refus dû à l'indisponibilité du modèle du classifieur, il s'agit du texte fixe Classifier unavailable

Contrôle de décision PermissionDenied

Les hooks PermissionDenied peuvent indiquer au modèle qu'il peut réessayer l'appel d'outil refusé. Renvoyez un objet JSON avec hookSpecificOutput.retry défini sur true :

{
  "hookSpecificOutput": {
    "hookEventName": "PermissionDenied",
    "retry": true
  }
}

Lorsque retry vaut true, Claude Code ajoute un message à la conversation indiquant au modèle qu'il peut réessayer l'appel d'outil. Claude Code n'annule pas le refus lui-même. Si votre hook ne renvoie pas de JSON, ou renvoie retry: false, le refus est maintenu et le modèle reçoit le message de rejet d'origine.

Claude Code ignore retry: true lorsque le classifieur n'a produit aucun verdict sur l'action : sa réponse n'a pas pu être analysée, ou un contrôle de sécurité distinct du mode auto a refusé la requête du classifieur lui-même. Pour ces refus, Claude Code indique déjà au modèle, dans le message de rejet, s'il doit réessayer plus tard ou passer à autre chose.

Notification

S'exécute lorsque Claude Code envoie des notifications. Correspond au type de notification. Omettez le matcher pour exécuter les hooks pour tous les types de notification.

Vous recevez ces événements de hook même lorsque les notifications de bureau sont désactivées : le paramètre preferredNotifChannel, y compris notifications_disabled, ne modifie que la manière dont vous êtes alerté, et non l'exécution de votre hook.

Matcher Quand il se déclenche
permission_prompt Claude a besoin que vous approuviez l'utilisation d'un outil ou la requête réseau d'une commande en sandbox, et la demande attend depuis environ six secondes
idle_prompt Claude a fini de répondre il y a environ 60 secondes et vous n'avez rien saisi depuis
auth_success L'authentification se termine
elicitation_dialog Un serveur MCP ouvre un formulaire d'élicitation et vous n'avez rien saisi depuis environ six secondes
elicitation_url_dialog Un serveur MCP vous demande d'ouvrir une URL dans le navigateur et vous n'avez rien saisi depuis environ six secondes
elicitation_complete Un serveur MCP signale qu'une élicitation en mode URL est terminée
elicitation_response Une réponse d'élicitation MCP est renvoyée au serveur
agent_needs_input Une session en arrière-plan commence à attendre votre saisie alors que la vue des agents est ouverte dans un terminal. Se déclenche également lorsqu'une session de terminal vous présente une question de configuration du terminal d'un coéquipier d'une équipe d'agents ou l'avis du mode auto concernant les frais de requêtes du classifieur et que vous n'avez rien saisi depuis environ six secondes
agent_completed Une session en arrière-plan se termine ou échoue. Se déclenche uniquement lorsque la vue des agents est ouverte dans un terminal
quota_auto_resume_fired Claude Code poursuit votre tâche après qu'une limite d'utilisation de claude.ai l'a mise en pause : à la réinitialisation, ou plus tôt lorsqu'une action que vous effectuez dans Claude Code pendant l'attente, comme l'ajout de crédits d'utilisation, la mise à niveau de votre forfait ou le changement de modèle, rend l'utilisation à nouveau disponible, avec l'exception du paramètre de modèle
quota_auto_resume_stale Une limite d'utilisation de claude.ai s'est réinitialisée pendant que votre ordinateur était en veille pendant plus d'environ 30 minutes. Claude Code attend que vous appuyiez sur Enter au lieu de poursuivre. Après une veille plus courte, il poursuit et déclenche quota_auto_resume_fired à la place
quota_auto_resume_disabled Claude Code met fin à son attente d'une limite d'utilisation de claude.ai sans poursuivre votre tâche : autoContinueAtUsageLimit a été désactivé ou la réinitialisation a été repoussée de plus de 24 heures pendant une attente que Claude Code a lancée de lui-même, la tâche poursuivie a continué d'atteindre la limite, ou la poursuite a été bloquée avant d'atteindre le modèle. Ne se déclenche pas lorsque vous appuyez sur Esc ou Ctrl+C, ou que vous choisissez Don't continue automatically

Les types quota_auto_resume_fired, quota_auto_resume_stale et quota_auto_resume_disabled nécessitent Claude Code v2.1.234 ou ultérieur.

Dans les sessions de terminal, permission_prompt pour la requête réseau d'une commande en sandbox nécessite Claude Code v2.1.246 ou ultérieur.

agent_needs_input pour la question de configuration du terminal d'un coéquipier nécessite Claude Code v2.1.248 ou ultérieur.

Claude Code minute permission_prompt différemment dans les sessions où il envoie les demandes de permission au callback canUseTool de l'Agent SDK, ce qui est la façon dont Claude Desktop et l'extension VS Code hébergent Claude Code :

  • Attendez-vous à permission_prompt environ six secondes après que Claude a demandé la permission. Claude Code ne le reporte pas pendant que vous tapez.
  • Si vous ou un hook PermissionRequest répondez plus tôt, Claude Code n'exécute pas permission_prompt.
  • Définissez CLAUDE_CODE_DISABLE_PERMISSION_PROMPT_NOTIFY_HOOKS sur 1 pour désactiver permission_prompt dans ces sessions.

Avant la v2.1.233, permission_prompt ne se déclenchait pas dans ces sessions.

Utilisez des matchers distincts pour exécuter différents gestionnaires selon le type de notification. Cette configuration déclenche un script d'alerte propre aux permissions lorsque Claude a besoin d'une approbation de permission, et une notification différente lorsque Claude est inactif :

{
  "hooks": {
    "Notification": [
      {
        "matcher": "permission_prompt",
        "hooks": [
          {
            "type": "command",
            "command": "/path/to/permission-alert.sh"
          }
        ]
      },
      {
        "matcher": "idle_prompt",
        "hooks": [
          {
            "type": "command",
            "command": "/path/to/idle-notification.sh"
          }
        ]
      }
    ]
  }
}

Entrée Notification

En plus des champs d'entrée communs, les hooks Notification reçoivent message avec le texte de la notification, un title facultatif et notification_type indiquant quel type s'est déclenché.

{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "cwd": "/Users/...",
  "hook_event_name": "Notification",
  "message": "Claude needs your permission",
  "title": "Permission needed",
  "notification_type": "permission_prompt"
}

Les hooks Notification ne peuvent ni bloquer ni modifier les notifications. Claude Code supprime leurs champs systemMessage et continue, mais émet toujours terminalSequence, sur lequel repose l'exemple de notification de bureau. Les hooks Notification sont destinés aux effets de bord, comme la transmission de la notification à un service externe.

SubagentStart

S'exécute lorsque Claude lance un sous-agent avec l'outil Agent, lorsque Claude reprend un sous-agent, et chaque fois qu'un coéquipier en processus d'une équipe d'agents traite un nouveau message. Prend en charge les matchers pour filtrer par nom de type d'agent. Pour les agents intégrés, il s'agit du nom de l'agent, comme general-purpose, Explore ou Plan. Pour les sous-agents personnalisés, il s'agit du champ name du frontmatter de l'agent, et non du nom de fichier.

Pour les sous-agents fournis par un plugin, le type d'agent est l'identifiant propre au plugin, comme my-plugin:reviewer, et non le simple nom du frontmatter. Les deux-points font passer un nom propre au plugin par le traitement des expressions régulières ; ancrez donc le matcher avec ^ et $ pour une correspondance exacte : ^my-plugin:reviewer$.

Entrée SubagentStart

En plus des champs d'entrée communs, les hooks SubagentStart reçoivent agent_id avec l'identifiant unique du sous-agent et agent_type avec le nom de l'agent sur lequel le matcher filtre.

{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "cwd": "/Users/...",
  "hook_event_name": "SubagentStart",
  "agent_id": "agent-abc123",
  "agent_type": "Explore"
}

Les hooks SubagentStart ne peuvent pas bloquer la création d'un sous-agent, mais ils peuvent injecter du contexte dans le sous-agent. En plus des champs de sortie JSON disponibles pour tous les hooks, vous pouvez renvoyer :

Champ Description
additionalContext Chaîne ajoutée au contexte du sous-agent au début de sa conversation, avant son premier prompt. Voir Ajouter du contexte pour Claude
{
  "hookSpecificOutput": {
    "hookEventName": "SubagentStart",
    "additionalContext": "Follow security guidelines for this task"
  }
}

Lorsque le hook s'exécute à nouveau pour le même sous-agent, Claude Code n'injecte le contexte renvoyé que si le contexte du sous-agent ne contient pas déjà la copie d'une exécution précédente. La copie injectée au lancement reste en place, ce qui préserve le cache de prompt du sous-agent. Après que la compaction automatique a supprimé cette copie, Claude Code injecte à nouveau le contexte de l'exécution suivante.

SubagentStop

S'exécute lorsqu'un sous-agent Claude Code a fini de répondre. Correspond au type d'agent, avec les mêmes valeurs que SubagentStart.

Entrée SubagentStop

En plus des champs d'entrée communs, les hooks SubagentStop reçoivent stop_hook_active, agent_id, agent_type, agent_transcript_path et last_assistant_message. Le champ agent_type est la valeur utilisée pour le filtrage par matcher. Le transcript_path est la transcription de la session principale, tandis que agent_transcript_path est la propre transcription du sous-agent, stockée dans un dossier subagents/ imbriqué. Le champ last_assistant_message contient le contenu textuel de la réponse finale du sous-agent, afin que les hooks puissent y accéder sans analyser le fichier de transcription.

Tous les événements SubagentStop ne proviennent pas d'un sous-agent lancé par Claude. Claude Code exécute également des agents internes pour certaines de ses propres fonctionnalités, comme les suggestions de prompt et les questions annexes /btw, et SubagentStop se déclenche aussi lorsque l'un d'eux se termine. Pour ces événements, agent_type est le nom de l'agent sous lequel la session elle-même s'exécute, tel que défini avec --agent ou le paramètre agent, et une chaîne vide lorsque la session s'exécute sans agent.

Un matcher qui nomme des types d'agents ne correspond pas à un agent_type vide. Un hook dont le matcher est omis, vaut "" ou "*", ou est une expression régulière correspondant à une chaîne vide, s'exécute également pour les événements dont l'agent_type est vide.

Sur Claude Code v2.1.271 ou ultérieur, un sous-agent qui s'exécute avec l'outil SubagentHandback transmet son rapport via cet outil avant de s'arrêter. Le champ last_assistant_message contient alors le texte de clôture du sous-agent, le cas échéant, qui n'est pas le rapport transmis. Le rapport est l'entrée message de cet appel, qu'un hook PreToolUse ou PostToolUse correspondant à SubagentHandback reçoit sous la forme tool_input.message.

Les hooks SubagentStop reçoivent également les tableaux background_tasks et session_crons décrits dans Entrée Stop. Ces deux tableaux portent sur la session parente, et non sur le sous-agent.

{
  "session_id": "abc123",
  "transcript_path": "~/.claude/projects/.../abc123.jsonl",
  "cwd": "/Users/...",
  "permission_mode": "default",
  "hook_event_name": "SubagentStop",
  "stop_hook_active": false,
  "agent_id": "def456",
  "agent_type": "Explore",
  "agent_transcript_path": "~/.claude/projects/.../abc123/subagents/agent-def456.jsonl",
  "last_assistant_message": "Analysis complete. Found 3 potential issues...",
  "background_tasks": [],
  "session_crons": []
}

Les hooks SubagentStop utilisent le même format de contrôle de décision que les hooks Stop, y compris hookSpecificOutput.additionalContext avec hookEventName défini sur "SubagentStop", pour un retour non lié à une erreur qui maintient le sous-agent en cours d'exécution. Renvoyer decision: "block" avec une reason maintient le sous-agent en cours d'exécution et lui transmet reason comme instruction suivante. Un hook qui bloque en se terminant avec le code 2 transmet son message stderr de la même manière. Pour injecter du contexte dans la session parente après le retour d'un sous-agent, utilisez plutôt un hook PostToolUse sur l'outil Agent.

TaskCreated

S'exécute lorsqu'une tâche est en cours de création via l'outil TaskCreate. Utilisez-le pour imposer des conventions de nommage, exiger des descriptions de tâches ou empêcher la création de certaines tâches. Dans une session sans les outils Task, cet événement ne se déclenche pas.

Les hooks TaskCreated ne prennent pas en charge les matchers et se déclenchent à chaque occurrence.

Entrée TaskCreated

En plus des champs d'entrée communs, les hooks TaskCreated reçoivent task_id, task_subject et, de manière facultative, task_description, teammate_name et team_name.

{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "cwd": "/Users/...",
  "hook_event_name": "TaskCreated",
  "task_id": "task-001",
  "task_subject": "Implement user authentication",
  "task_description": "Add login and signup endpoints",
  "teammate_name": "implementer",
  "team_name": "session-a1b2c3d4"
}
Champ Description
task_id Identifiant de la tâche en cours de création
task_subject Titre de la tâche
task_description Description détaillée de la tâche. Peut être absent
teammate_name Nom du coéquipier qui crée la tâche. Peut être absent
team_name Déprécié. Nom d'équipe dérivé de la session ; sera supprimé dans une version future

Contrôle de décision TaskCreated

Un hook TaskCreated peut bloquer la création de deux manières. Dans les deux cas, Claude Code supprime la tâche et renvoie votre message à Claude comme erreur de l'outil. Claude Code ignore continue: false pour cet événement et Claude continue de travailler.

  • Code de sortie 2 : Claude Code renvoie le texte de stderr comme message.
  • JSON {"decision": "block", "reason": "..."} : Claude Code renvoie reason comme message.

Cet exemple bloque les tâches dont le sujet ne respecte pas le format requis :

#!/bin/bash
INPUT=$(cat)
TASK_SUBJECT=$(echo "$INPUT" | jq -r '.task_subject')

if [[ ! "$TASK_SUBJECT" =~ ^\[TICKET-[0-9]+\] ]]; then
  echo "Task subject must start with a ticket number, e.g. '[TICKET-123] Add feature'" >&2
  exit 2
fi

exit 0

TaskCompleted

S'exécute lorsqu'une tâche est en cours de marquage comme terminée. Cela se déclenche dans deux situations : lorsqu'un agent marque explicitement une tâche comme terminée via l'outil TaskUpdate, ou lorsqu'un coéquipier d'une équipe d'agents termine son tour avec des tâches en cours. Utilisez-le pour imposer des critères d'achèvement, comme la réussite des tests ou des vérifications de lint, avant qu'une tâche puisse être clôturée.

Les hooks TaskCompleted ne prennent pas en charge les matchers et se déclenchent à chaque occurrence.

Entrée TaskCompleted

En plus des champs d'entrée communs, les hooks TaskCompleted reçoivent task_id, task_subject et, de manière facultative, task_description, teammate_name et team_name.

{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "cwd": "/Users/...",
  "permission_mode": "default",
  "hook_event_name": "TaskCompleted",
  "task_id": "task-001",
  "task_subject": "Implement user authentication",
  "task_description": "Add login and signup endpoints",
  "teammate_name": "implementer",
  "team_name": "session-a1b2c3d4"
}
Champ Description
task_id Identifiant de la tâche en cours d'achèvement
task_subject Titre de la tâche
task_description Description détaillée de la tâche. Peut être absent
teammate_name Nom du coéquipier qui termine la tâche. Peut être absent
team_name Déprécié. Nom d'équipe dérivé de la session ; sera supprimé dans une version future

Contrôle de décision TaskCompleted

Les hooks TaskCompleted offrent deux manières de contrôler l'achèvement des tâches :

  • Code de sortie 2 : la tâche n'est pas marquée comme terminée et le message stderr est renvoyé au modèle comme retour.
  • JSON {"continue": false, "stopReason": "..."} : lorsque l'événement a été déclenché par un coéquipier terminant son tour, arrête complètement le coéquipier, comme le comportement du hook Stop. Le stopReason est affiché à l'utilisateur. Lorsque l'événement a été déclenché par l'outil TaskUpdate, Claude Code ignore continue: false ; le code de sortie 2 bloque toujours l'achèvement.

Cet exemple exécute les tests et bloque l'achèvement de la tâche s'ils échouent :

#!/bin/bash
INPUT=$(cat)
TASK_SUBJECT=$(echo "$INPUT" | jq -r '.task_subject')

# Run the test suite
if ! npm test 2>&1; then
  echo "Tests not passing. Fix failing tests before completing: $TASK_SUBJECT" >&2
  exit 2
fi

exit 0

Stop

S'exécute lorsque l'agent principal de Claude Code a fini de répondre. Ne s'exécute pas si l'arrêt est dû à une interruption de l'utilisateur. Les erreurs d'API déclenchent StopFailure à la place.

Entrée Stop

En plus des champs d'entrée communs, les hooks Stop reçoivent stop_hook_active, last_assistant_message, background_tasks et session_crons. Le champ stop_hook_active vaut true lorsque Claude Code poursuit déjà à la suite d'un hook Stop. Vérifiez cette valeur ou traitez la transcription pour éviter de bloquer sur une condition qui ne se résoudra jamais. Claude Code applique un plafond de 8 poursuites consécutives : après que les hooks Stop ont prolongé le tour huit fois de suite, Claude Code passe outre le blocage suivant et termine le tour. Pour relever ce plafond, définissez CLAUDE_CODE_STOP_HOOK_BLOCK_CAP.

Le champ last_assistant_message contient le contenu textuel de la réponse finale de Claude, afin que les hooks puissent y accéder sans analyser le fichier de transcription. Pour les hooks qui agissent sur le tour qui vient de se terminer, comme les hooks de lecture à voix haute ou de notification, utilisez ce champ plutôt que de lire transcript_path : il n'est pas garanti, sur toutes les versions, que le fichier de transcription contienne le message final au moment de Stop.

Les tableaux background_tasks et session_crons permettent aux hooks de distinguer « la session est terminée » de « la session est en pause en attendant qu'un travail en arrière-plan la réveille ». Les deux tableaux sont présents lorsque le registre des tâches est accessible et sont vides lorsque rien n'est en cours ni planifié.

Chaque entrée de background_tasks décrit une tâche en cours et utilise ces champs :

Champ Description
id Identifiant de la tâche
type Libellé lisible du type de tâche, comme shell, subagent, monitor, workflow, teammate, cloud session ou MCP task. Chaque libellé identifie la fonctionnalité de Claude Code qui a créé la tâche. Revient au discriminant brut pour les types non reconnus
status Statut actuel de la tâche
description Description en texte libre, plafonnée à 1000 caractères avec un marqueur … [+N chars] dans la chaîne en cas de troncature
command Ligne de commande shell, plafonnée à 1000 caractères. Présent uniquement pour les tâches shell
agent_type Nom du type de sous-agent. Présent uniquement pour les tâches subagent
server Nom du serveur MCP. Présent uniquement pour les tâches monitor et MCP task
tool Nom de l'outil MCP. Présent uniquement pour les tâches monitor et MCP task
name Nom du workflow. Présent uniquement pour les tâches workflow

Chaque entrée de session_crons décrit un réveil planifié limité à la session, provenant de CronCreate, ScheduleWakeup et /loop :

Champ Description
id Identifiant de la tâche cron
schedule Expression cron, par exemple 0 9 * * 1-5
recurring false pour les réveils ponctuels dont la planification encode une seule heure de déclenchement, true pour les tâches qui se redéclenchent à chaque correspondance
prompt Prompt soumis lorsque le cron se déclenche, plafonné à 1000 caractères avec le même marqueur … [+N chars]

Cet exemple montre une entrée Stop avec une tâche shell en cours et un cron récurrent :

{
  "session_id": "abc123",
  "transcript_path": "~/.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "cwd": "/Users/...",
  "permission_mode": "default",
  "hook_event_name": "Stop",
  "stop_hook_active": true,
  "last_assistant_message": "I've completed the refactoring. Here's a summary...",
  "background_tasks": [
    {
      "id": "task-001",
      "type": "shell",
      "status": "running",
      "description": "tail logs",
      "command": "tail -f /var/log/syslog"
    }
  ],
  "session_crons": [
    {
      "id": "cron-001",
      "schedule": "0 9 * * 1-5",
      "recurring": true,
      "prompt": "check the build"
    }
  ]
}

Contrôle de décision Stop

Les hooks Stop et SubagentStop peuvent contrôler si Claude continue. En plus des champs de sortie JSON disponibles pour tous les hooks, votre script de hook peut renvoyer ces champs propres à l'événement :

Champ Description
decision "block" empêche Claude de s'arrêter. Omettez-le pour permettre à Claude de s'arrêter
reason Obligatoire lorsque decision vaut "block". Indique à Claude pourquoi il doit continuer
hookSpecificOutput.additionalContext Retour non lié à une erreur pour Claude. La conversation continue afin que Claude puisse agir en conséquence, mais contrairement à decision: "block", il est affiché dans la transcription comme un retour de hook plutôt que comme une erreur de hook

Un hook qui bloque en se terminant avec le code 2 est traité de la même manière que reason : Claude reçoit le message stderr comme explication de la raison pour laquelle il doit continuer.

{
  "decision": "block",
  "reason": "Must be provided when Claude is blocked from stopping"
}

Utilisez additionalContext lorsque le hook fonctionne comme prévu et donne des indications à Claude, comme « exécuter la suite de tests avant de terminer ». Il fait continuer la conversation avec les mêmes protections contre les boucles que decision: "block", à savoir l'entrée stop_hook_active et le plafond de 8 poursuites consécutives, mais la transcription l'étiquette Stop hook feedback et aucune notification d'erreur de hook n'est affichée :

{
  "hookSpecificOutput": {
    "hookEventName": "Stop",
    "additionalContext": "Please run the test suite before finishing"
  }
}

StopFailure

S'exécute à la place de Stop lorsque le tour se termine en raison d'une erreur d'API. Claude Code ignore la sortie et le code de sortie du hook, à l'exception de terminalSequence. Utilisez-le pour journaliser les échecs, envoyer des alertes ou prendre des mesures de récupération lorsque Claude ne peut pas terminer une réponse en raison de limites de débit, de problèmes d'authentification ou d'autres erreurs d'API.

Entrée StopFailure

En plus des champs d'entrée communs, les hooks StopFailure reçoivent error, un error_details facultatif et un last_assistant_message facultatif. Le champ error identifie le type d'erreur et est utilisé pour le filtrage par matcher.

Champ Description
error Type d'erreur : rate_limit, overloaded, authentication_failed, oauth_org_not_allowed, account_on_hold, billing_error, invalid_request, model_not_found, server_error, max_output_tokens, cloud_credential_error ou unknown
error_details Détails supplémentaires sur l'erreur, lorsqu'ils sont disponibles
last_assistant_message Le texte d'erreur affiché dans la conversation. Contrairement à Stop et SubagentStop, où ce champ contient la sortie conversationnelle de Claude, pour StopFailure il contient la chaîne d'erreur de l'API elle-même, comme "API Error: Rate limit reached"
{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "cwd": "/Users/...",
  "hook_event_name": "StopFailure",
  "error": "rate_limit",
  "error_details": "429 Too Many Requests",
  "last_assistant_message": "API Error: Rate limit reached"
}

Les hooks StopFailure n'ont pas de contrôle de décision. Ils s'exécutent uniquement à des fins de notification et de journalisation.

TeammateIdle

S'exécute lorsqu'un coéquipier d'une équipe d'agents est sur le point de devenir inactif après avoir terminé son tour. Utilisez-le pour imposer des contrôles qualité avant qu'un coéquipier n'arrête de travailler, par exemple en exigeant la réussite des vérifications de lint ou en vérifiant l'existence des fichiers de sortie.

Les hooks TeammateIdle ne prennent pas en charge les matchers et se déclenchent à chaque occurrence.

Entrée TeammateIdle

En plus des champs d'entrée communs, les hooks TeammateIdle reçoivent teammate_name et team_name.

{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "cwd": "/Users/...",
  "permission_mode": "default",
  "hook_event_name": "TeammateIdle",
  "teammate_name": "researcher",
  "team_name": "session-a1b2c3d4"
}
Champ Description
teammate_name Nom du coéquipier qui est sur le point de devenir inactif
team_name Déprécié. Nom d'équipe dérivé de la session ; sera supprimé dans une version future

Contrôle de décision TeammateIdle

Les hooks TeammateIdle offrent deux manières de contrôler le comportement du coéquipier :

  • Code de sortie 2 : le coéquipier reçoit le message stderr comme retour et continue de travailler au lieu de devenir inactif.
  • JSON {"continue": false, "stopReason": "..."} : arrête complètement le coéquipier, comme le comportement du hook Stop. Le stopReason est affiché à l'utilisateur.

Cet exemple vérifie qu'un artefact de build existe avant de permettre à un coéquipier de devenir inactif :

#!/bin/bash

if [ ! -f "./dist/output.js" ]; then
  echo "Build artifact missing. Run the build before stopping." >&2
  exit 2
fi

exit 0

ConfigChange

S'exécute lorsqu'un fichier de configuration change pendant une session. Utilisez-le pour auditer les modifications de paramètres, appliquer des politiques de sécurité ou bloquer les modifications non autorisées des fichiers de configuration.

Claude Code exécute les hooks ConfigChange lorsqu'un fichier de paramètres, un fichier de politique gérée ou un fichier de skill change. Pour la politique gérée, il ne les exécute que lorsque managed-settings.json ou un fichier de managed-settings.d/ change. Il applique les paramètres gérés par le serveur et les modifications des préférences gérées macOS ou de la politique du registre Windows sans les exécuter. Sous WSL avec wslInheritsWindowsSettings, il applique également un fichier de paramètres gérés modifié côté Windows lors de son interrogation de politique, sans les exécuter.

Le matcher filtre sur la source de configuration :

Matcher Quand il se déclenche
user_settings ~/.claude/settings.json change
project_settings .claude/settings.json change
local_settings .claude/settings.local.json change
policy_settings managed-settings.json ou un fichier de managed-settings.d/ change
skills Un fichier de skill dans .claude/skills/ change

Cet exemple journalise toutes les modifications de configuration à des fins d'audit de sécurité :

{
  "hooks": {
    "ConfigChange": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/audit-config-change.sh",
            "args": []
          }
        ]
      }
    ]
  }
}

Entrée ConfigChange

En plus des champs d'entrée communs, les hooks ConfigChange reçoivent source et, de manière facultative, file_path. Le champ source indique quel type de configuration a changé, et file_path fournit le chemin du fichier précis qui a été modifié.

{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "cwd": "/Users/...",
  "hook_event_name": "ConfigChange",
  "source": "project_settings",
  "file_path": "/Users/.../my-project/.claude/settings.json"
}

Contrôle de décision ConfigChange

Les hooks ConfigChange peuvent empêcher des modifications de configuration de prendre effet. Utilisez le code de sortie 2 ou une decision JSON pour empêcher la modification. En cas de blocage, les nouveaux paramètres ne sont pas appliqués à la session en cours.

Champ Description
decision "block" empêche l'application de la modification de configuration. Omettez-le pour autoriser la modification
reason Accepté mais jamais affiché
{
  "decision": "block",
  "reason": "Configuration changes to project settings require admin approval"
}

Les modifications policy_settings ne peuvent pas être bloquées. Les hooks se déclenchent toujours pour les sources policy_settings lorsqu'un fichier de paramètres gérés sur la machine change, ce qui vous permet de les utiliser pour journaliser ces modifications, mais toute décision de blocage est ignorée. Cela garantit que les paramètres gérés par l'entreprise prennent toujours effet. Claude Code n'exécute pas les hooks ConfigChange lorsque des paramètres gérés par le serveur arrivent ou sont actualisés.

Claude Code applique la décision de blocage issue de la sortie JSON d'un hook ConfigChange et supprime systemMessage et continue. Une modification bloquée n'affiche aucun message, ni à vous ni à Claude, que vous bloquiez avec reason ou avec stderr et le code de sortie 2. Claude Code écrit seulement une ligne dans le log de débogage.

CwdChanged

S'exécute lorsqu'une commande shell dans la conversation principale change le répertoire de travail, par exemple lorsque Claude exécute une commande cd. Utilisez-le pour réagir aux changements de répertoire : recharger des variables d'environnement, activer des chaînes d'outils propres au projet ou exécuter automatiquement des scripts de configuration. Se combine avec FileChanged pour des outils comme direnv qui gèrent l'environnement par répertoire.

Les hooks CwdChanged ont accès à CLAUDE_ENV_FILE. Les variables écrites dans ce fichier persistent dans les commandes Bash suivantes jusqu'au prochain événement CwdChanged, moment où Claude Code les efface.

CwdChanged ne prend pas en charge les matchers et se déclenche à chaque occurrence.

Entrée CwdChanged

En plus des champs d'entrée communs, les hooks CwdChanged reçoivent old_cwd et new_cwd.

{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../transcript.jsonl",
  "cwd": "/Users/my-project/src",
  "hook_event_name": "CwdChanged",
  "old_cwd": "/Users/my-project",
  "new_cwd": "/Users/my-project/src"
}

Sortie CwdChanged

En plus des champs de sortie JSON disponibles pour tous les hooks, les hooks CwdChanged peuvent renvoyer watchPaths pour définir dynamiquement les chemins de fichiers surveillés par FileChanged :

Champ Description
watchPaths Tableau de chemins absolus. Remplace la liste de surveillance dynamique actuelle. Les chemins issus de votre configuration matcher sont toujours surveillés. Renvoyer un tableau vide efface la liste dynamique, ce qui est typique lors de l'entrée dans un nouveau répertoire

Les hooks CwdChanged n'ont pas de contrôle de décision. Ils ne peuvent pas bloquer le changement de répertoire.

Claude Code lit watchPaths et systemMessage dans leur sortie JSON et supprime continue. Dans les sessions interactives, il affiche le systemMessage sous forme de brève notification dans le terminal. Le message n'atteint pas le flux de messages du SDK.

DirectoryAdded

S'exécute après que vous avez ajouté un répertoire de travail en cours de session avec la commande /add-dir, ou après qu'un client SDK en a ajouté un avec la requête de contrôle register_repo_root. Utilisez-le pour préparer un dépôt nouvellement ajouté, par exemple en installant ses dépendances.

Claude Code ne déclenche pas cet événement lorsque :

  • Vous passez un répertoire avec le flag de démarrage --add-dir ; SessionStart couvre ces répertoires
  • Vous ajoutez un répertoire dans l'onglet Workspace de /permissions
  • Vous ajoutez un répertoire qui est déjà un répertoire de travail ou qui se trouve à l'intérieur de l'un d'eux

Claude Code déclenche DirectoryAdded après avoir actualisé l'état du sandbox et des permissions, de sorte que les outils en sandbox voient déjà le nouveau répertoire lorsque votre hook s'exécute. Les commandes des hooks elles-mêmes s'exécutent hors sandbox.

Claude Code n'attend pas le hook : l'ajout se termine immédiatement, et le hook s'exécute en arrière-plan avec le délai d'expiration par défaut de 600 secondes.

Le matcher filtre sur la manière dont le répertoire a été ajouté :

Matcher Quand il se déclenche
slash_command Vous ajoutez un répertoire avec /add-dir
register_repo_root Un client SDK ajoute un répertoire avec la requête de contrôle register_repo_root

Entrée DirectoryAdded

En plus des champs d'entrée communs, les hooks DirectoryAdded reçoivent directory et source.

Champ Description
directory Chemin absolu du répertoire qui a été ajouté
source La manière dont le répertoire a été ajouté : "slash_command" pour /add-dir ou "register_repo_root" pour la requête de contrôle du SDK
{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../transcript.jsonl",
  "cwd": "/Users/my-project",
  "hook_event_name": "DirectoryAdded",
  "directory": "/Users/my-other-repo",
  "source": "slash_command"
}

Les hooks DirectoryAdded n'ont pas de contrôle de décision. Ils ne peuvent pas bloquer l'ajout, qui est déjà terminé lorsque le hook s'exécute. Claude Code supprime le champ continue de leur sortie JSON et présente le reste différemment selon la source :

  • slash_command : Claude Code transmet le systemMessage du hook à Claude comme contexte au tour suivant de la conversation, plutôt que de vous l'afficher. Le nombre de hooks en échec apparaît dans la transcription. La sortie complète des échecs est envoyée dans le log de débogage
  • register_repo_root : Claude Code écrit la sortie systemMessage et la sortie des échecs uniquement dans le log de débogage

FileChanged

S'exécute lorsqu'un fichier surveillé change sur le disque. Claude Code détecte les modifications avec un observateur du système de fichiers, et non en inspectant les appels d'outils ; il exécute donc le hook quelle que soit l'origine de la modification : un appel d'outil Edit ou Write, un script que Claude exécute avec Bash, ou un processus entièrement extérieur à Claude Code. Un usage courant consiste à recharger des variables d'environnement lorsque des fichiers de configuration du projet changent.

Le matcher de cet événement joue deux rôles :

  • Construire la liste de surveillance : la valeur est découpée sur | et chaque segment est enregistré comme un nom de fichier littéral dans le répertoire de travail ; ainsi ".envrc|.env" surveille exactement ces deux fichiers. Les expressions régulières ne sont pas utiles ici : une valeur comme ^\.env surveillerait un fichier littéralement nommé ^\.env.
  • Filtrer les hooks qui s'exécutent : lorsqu'un fichier surveillé change, la même valeur filtre les groupes de hooks qui s'exécutent selon les règles standard des matchers, appliquées au nom de base du fichier modifié.

Cet exemple normalise les fins de ligne de data.csv après toute modification, y compris lorsqu'une commande Bash ou un script externe réécrit le fichier :

{
  "hooks": {
    "FileChanged": [
      {
        "matcher": "data.csv",
        "hooks": [
          {
            "type": "command",
            "command": "/path/to/normalize-line-endings.sh"
          }
        ]
      }
    ]
  }
}

Le hook lit le chemin absolu du fichier modifié dans le champ file_path de l'entrée JSON sur stdin. Sa garde grep teste la même chose que ce que perl supprime, un CR en fin de ligne, de sorte que l'exécution qui suit une normalisation se termine sans toucher au fichier. Une garde plus permissive boucle indéfiniment, car perl -i réécrit le fichier même lorsqu'il ne substitue rien et Claude Code réexécute le hook après chaque réécriture. Enregistrez ce script sous /path/to/normalize-line-endings.sh et rendez-le exécutable :

#!/bin/bash
FILE=$(jq -r .file_path)
if grep -q $'\r$' "$FILE"; then
  perl -pi -e 's/\r$//' "$FILE"
fi

Pour vérifier que le hook fonctionne, demandez à Claude d'ajouter une ligne CRLF à data.csv avec une commande Bash. Claude Code exécute le hook et le fichier se retrouve avec des fins de ligne LF.

Pour surveiller des fichiers que vous ne pouvez pas nommer à l'avance, renvoyez watchPaths depuis un hook afin de mettre à jour dynamiquement la liste de surveillance. Claude Code ne démarre l'observateur que lorsqu'un élément nomme un fichier à surveiller ; amorcez donc la liste avec un groupe FileChanged dont le matcher nomme au moins un fichier, ou avec un hook SessionStart ou CwdChanged qui renvoie watchPaths. Le matcher filtre toujours les groupes de hooks qui s'exécutent lorsqu'un fichier surveillé change ; donnez donc au groupe qui gère les chemins dynamiques un matcher omis, qui correspond à tous les fichiers surveillés et n'ajoute rien à la liste de surveillance. Un matcher "*" correspond également à tous les fichiers, mais Claude Code l'enregistre dans la liste de surveillance comme n'importe quelle autre valeur, en tant que fichier littéral nommé *.

Les hooks FileChanged ont accès à CLAUDE_ENV_FILE. Les variables écrites dans ce fichier persistent dans les commandes Bash suivantes jusqu'au prochain événement CwdChanged, moment où Claude Code les efface.

Entrée FileChanged

En plus des champs d'entrée communs, les hooks FileChanged reçoivent file_path et event.

Champ Description
file_path Chemin absolu du fichier qui a changé
event Ce qui s'est produit : "change" pour un fichier modifié, "add" pour un fichier créé ou "unlink" pour un fichier supprimé
{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../transcript.jsonl",
  "cwd": "/Users/my-project",
  "hook_event_name": "FileChanged",
  "file_path": "/Users/my-project/.envrc",
  "event": "change"
}

Sortie FileChanged

En plus des champs de sortie JSON disponibles pour tous les hooks, les hooks FileChanged peuvent renvoyer watchPaths pour mettre à jour dynamiquement les chemins de fichiers surveillés :

Champ Description
watchPaths Tableau de chemins absolus. Remplace la liste de surveillance dynamique actuelle. Les chemins issus de votre configuration matcher sont toujours surveillés. Utilisez-le lorsque votre script de hook découvre des fichiers supplémentaires à surveiller en fonction du fichier modifié

Les hooks FileChanged n'ont pas de contrôle de décision. Ils ne peuvent pas empêcher la modification du fichier.

Claude Code lit watchPaths et systemMessage dans leur sortie JSON et supprime continue. Dans les sessions interactives, il affiche le systemMessage sous forme de brève notification dans le terminal. Le message n'atteint pas le flux de messages du SDK.

WorktreeCreate

S'exécute lorsqu'un worktree est en cours de création, que ce soit depuis claude --worktree, depuis un sous-agent utilisant isolation: "worktree", ou pour une session en arrière-plan que Claude Code isole dans son propre worktree. Par défaut, Claude Code crée la copie de travail isolée avec git worktree. Configurer un hook WorktreeCreate remplace ce comportement git par défaut, ce qui vous permet d'utiliser un autre système de gestion de versions comme SVN, Perforce ou Mercurial.

Comme le hook remplace entièrement le comportement par défaut, .worktreeinclude n'est pas traité. Si vous devez copier des fichiers de configuration locaux comme .env dans le nouveau worktree, faites-le dans votre script de hook.

Le hook doit renvoyer le chemin du répertoire du worktree créé. Claude Code utilise ce chemin comme répertoire de travail pour la session isolée. Voir Sortie WorktreeCreate pour savoir comment chaque type de hook renvoie le chemin.

Claude Code tient compte de la réussite du hook et du chemin renvoyé, et supprime systemMessage et continue.

Cet exemple crée une copie de travail SVN et affiche le chemin que Claude Code doit utiliser. Remplacez l'URL du dépôt par la vôtre :

{
  "hooks": {
    "WorktreeCreate": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "bash -c 'NAME=$(jq -r .name); DIR=\"$HOME/.claude/worktrees/$NAME\"; svn checkout https://svn.example.com/repo/trunk \"$DIR\" >&2 && echo \"$DIR\"'"
          }
        ]
      }
    ]
  }
}

Le hook lit le name du worktree dans l'entrée JSON sur stdin, extrait une nouvelle copie dans un nouveau répertoire et affiche le chemin du répertoire. L'echo de la dernière ligne est ce que Claude Code lit comme chemin du worktree. Redirigez toute autre sortie vers stderr afin qu'elle n'interfère pas avec le chemin.

Entrée WorktreeCreate

En plus des champs d'entrée communs, les hooks WorktreeCreate reçoivent le champ name. Il s'agit d'un identifiant de type slug pour le nouveau worktree, soit spécifié par l'utilisateur, soit généré automatiquement, par exemple bold-oak-a3f2.

{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "cwd": "/Users/...",
  "hook_event_name": "WorktreeCreate",
  "name": "feature-auth"
}

Sortie de WorktreeCreate

Les hooks WorktreeCreate n'utilisent pas le modèle de décision standard autoriser/bloquer. C'est plutôt la réussite ou l'échec du hook qui détermine le résultat. Le hook doit renvoyer le chemin vers le répertoire du worktree créé :

  • Hooks de commande (type: "command") : affichez le chemin sur la dernière ligne non vide de stdout. Claude Code supprime les codes d'échappement ANSI avant de lire cette ligne, de sorte que les bannières de démarrage du shell affichées avant votre echo sont ignorées. Redirigez toute autre sortie du hook vers stderr.
  • Hooks HTTP (type: "http") : renvoyez { "hookSpecificOutput": { "hookEventName": "WorktreeCreate", "worktreePath": "/absolute/path" } } dans le corps de la réponse.

Si le hook échoue ou ne produit aucun chemin, la création du worktree échoue avec une erreur.

Claude Code résout un chemin relatif par rapport au répertoire dans lequel le hook s'est exécuté, en réduisant les éventuels segments . ou .. qu'il contient. Si le chemin obtenu n'est pas un répertoire dans lequel Claude Code peut entrer, la session affiche une erreur indiquant le chemin et se termine avec le code 1.

Claude Code refuse un chemin absolu contenant des segments . ou .., ainsi que tout chemin passant par un lien symbolique situé sous la racine du dépôt, car un lien symbolique commité dans le dépôt pourrait rediriger le worktree en dehors de celui-ci. L'erreur indique le composant rejeté. Renvoyez un chemin normalisé qui ne passe pas par un lien symbolique à l'intérieur du dépôt. Avant la v2.1.216, la création du worktree suivait le chemin du hook sans ce contrôle.

WorktreeRemove

S'exécute lorsqu'un worktree est en cours de suppression. C'est le pendant de nettoyage de WorktreeCreate. L'événement se déclenche lorsque :

  • vous quittez une session --worktree et choisissez de la supprimer
  • un sous-agent avec isolation: "worktree" se termine
  • vous supprimez une session en arrière-plan dont le worktree a été créé par le hook

Pour les worktrees basés sur git, Claude Code gère automatiquement le nettoyage avec git worktree remove. Si vous avez configuré un hook WorktreeCreate, associez-le à un hook WorktreeRemove pour contrôler le nettoyage des worktrees qu'il crée :

  • Aucun hook WorktreeRemove : lorsque vous quittez une session --worktree et choisissez la suppression, Claude Code se rabat sur git worktree remove --force sur le chemin renvoyé par votre hook WorktreeCreate, de sorte qu'un worktree reconnu par git est supprimé. Un worktree que git ne reconnaît pas, par exemple un worktree créé par votre hook avec un système de gestion de versions autre que git, reste sur le disque. Pour savoir ce que la suppression d'une session en arrière-plan fait d'un worktree créé par un hook, consultez les règles de suppression de la vue agent.
  • Le hook se termine avec le code 0 : le worktree est considéré comme supprimé. Claude Code ne lit rien d'autre du hook ; assurez-vous donc que votre hook a bien supprimé le répertoire.
  • Le hook se termine avec un code non nul : la suppression échoue si le répertoire situé à worktree_path existe toujours ensuite, et le worktree reste sur le disque sans solution de repli git. Un hook qui a supprimé le répertoire avant de se terminer avec un code non nul est considéré comme ayant supprimé le worktree. Pour savoir comment l'échec est signalé, consultez Entrée de WorktreeRemove.

Claude Code ne supprime jamais une branche appartenant à un worktree créé par un hook, car il ne connaît que le chemin renvoyé par votre hook WorktreeCreate. Si votre hook WorktreeCreate crée une branche, supprimez-la dans votre hook WorktreeRemove.

Claude Code ignore les champs de sortie JSON d'un hook WorktreeRemove, tels que systemMessage et continue.

Lors de la suppression d'une session en arrière-plan, Claude Code vérifie le chemin du worktree enregistré avant d'exécuter le hook et refuse un chemin qui est un lien symbolique ou qui passe par un lien symbolique sous la racine du dépôt. Le hook s'exécute pour un worktree contenant encore des fichiers uniquement lorsque vous confirmez la suppression dans la vue agent ; pour un tel worktree, claude rm conserve plutôt la session et le worktree. Avant la v2.1.216, le hook s'exécutait sur le chemin enregistré sans ces vérifications.

Claude Code transmet le chemin renvoyé par WorktreeCreate sous la forme worktree_path dans l'entrée du hook. Cet exemple lit ce chemin et supprime le répertoire :

{
  "hooks": {
    "WorktreeRemove": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "bash -c 'jq -r .worktree_path | xargs rm -rf'"
          }
        ]
      }
    ]
  }
}

Entrée de WorktreeRemove

En plus des champs d'entrée communs, les hooks WorktreeRemove reçoivent le champ worktree_path, qui est le chemin absolu vers le worktree en cours de suppression.

{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "cwd": "/Users/...",
  "hook_event_name": "WorktreeRemove",
  "worktree_path": "/Users/.../my-project/.claude/worktrees/feature-auth"
}

Le code de sortie d'un hook WorktreeRemove détermine le résultat. Lorsqu'un hook se termine avec un code non nul et que le répertoire situé à worktree_path existe toujours ensuite, la suppression échoue :

  • Le worktree reste sur le disque, et la commande et le stderr du hook sont envoyés au log de débogage.
  • Si vous supprimiez une session en arrière-plan, la session est également conservée. Le message de refus dans la vue agent indique comment le hook s'est terminé, par exemple exited 1, cite le début de son stderr et précise si une nouvelle suppression de la session supprime malgré tout le répertoire.

PreCompact

S'exécute juste avant que Claude Code n'effectue une opération de compaction.

La valeur du matcher indique si la compaction a été déclenchée manuellement ou automatiquement :

Matcher Quand il se déclenche
manual /compact
auto Compaction automatique lorsque la conversation atteint la fenêtre de compaction automatique

Terminez avec le code 2 pour bloquer la compaction. Pour un /compact manuel, le message stderr est affiché à l'utilisateur. Vous pouvez également bloquer en renvoyant du JSON avec "decision": "block".

Le blocage de la compaction automatique a des effets différents selon le moment où elle se déclenche. Si la compaction a été déclenchée de manière proactive avant la limite de contexte, Claude Code l'ignore et la conversation se poursuit sans être compactée. Si la compaction a été déclenchée pour récupérer d'une erreur de limite de contexte déjà renvoyée par l'API, l'erreur sous-jacente remonte et la requête en cours échoue.

Claude Code ignore les champs systemMessage et continue d'un hook PreCompact.

Entrée de PreCompact

En plus des champs d'entrée communs, les hooks PreCompact reçoivent trigger et custom_instructions. Pour manual, custom_instructions contient ce que l'utilisateur transmet à /compact et vaut null lorsqu'il ne transmet rien. Pour auto, custom_instructions vaut null.

{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "cwd": "/Users/...",
  "hook_event_name": "PreCompact",
  "trigger": "manual",
  "custom_instructions": null
}

PostCompact

S'exécute après que Claude Code a terminé une opération de compaction. Utilisez cet événement pour réagir au nouvel état compacté, par exemple pour journaliser le résumé généré ou mettre à jour un état externe. Claude Code ignore les champs systemMessage et continue d'un hook PostCompact.

Les mêmes valeurs de matcher s'appliquent que pour PreCompact :

Matcher Quand il se déclenche
manual Après /compact
auto Après la compaction automatique lorsque la conversation atteint la fenêtre de compaction automatique

Entrée de PostCompact

En plus des champs d'entrée communs, les hooks PostCompact reçoivent trigger et compact_summary. Le champ compact_summary contient le résumé de la conversation généré par l'opération de compaction.

{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "cwd": "/Users/...",
  "hook_event_name": "PostCompact",
  "trigger": "manual",
  "compact_summary": "Summary of the compacted conversation..."
}

Les hooks PostCompact n'ont aucun contrôle de décision. Ils ne peuvent pas influer sur le résultat de la compaction, mais peuvent effectuer des tâches de suivi.

PreModelSwitch

S'exécute avant que Claude Code n'applique un changement de modèle demandé par vous ou par un client. Utilisez-le pour bloquer un changement, exiger une confirmation ou afficher ce que le changement coûtera avant qu'il n'ait lieu.

PreModelSwitch nécessite Claude Code v2.1.251 ou une version ultérieure. Claude Code l'exécute pour ces demandes :

  • /model <name> et le sélecteur /model
  • Le sélecteur de modèle Option+P ou Alt+P
  • Le paramètre Model dans /config
  • L'activation du mode rapide lorsque celle-ci change le modèle de la session
  • Une requête set_model, ou un changement de modèle dans une requête apply_flag_settings, provenant d'un hôte Agent SDK ou de Remote Control

Claude Code n'exécute pas les hooks PreModelSwitch pour les changements qu'il effectue de lui-même, comme un basculement automatique vers un modèle de secours ou la restauration du modèle lorsque vous reprenez une session. Ces changements atteignent uniquement PostModelSwitch.

Claude Code compare le matcher au nom canonique du modèle vers lequel la session bascule, en ignorant tout suffixe [1m]. Un alias tel que opus, un identifiant de modèle daté et un identifiant propre à un fournisseur, comme un identifiant de modèle Amazon Bedrock, correspondent tous au même nom canonique vers lequel ils se résolvent ; ainsi, claude-opus-5 couvre toutes les écritures d'Opus 5.

Lorsque Claude Code ne peut pas déterminer un nom canonique pour la cible, par exemple un identifiant de modèle personnalisé que seule votre passerelle LLM connaît, il exécute tous les hooks PreModelSwitch quel que soit le matcher. Un hook qui bloque doit donc vérifier to_model dans son entrée plutôt que de se fier uniquement au matcher.

Écrivez le matcher sous la forme d'un nom exact, d'une liste séparée par des | telle que claude-opus-4-6|claude-opus-5, ou d'une expression régulière telle que .*opus.*. Cet exemple utilise un matcher à nom exact et vérifie également to_model dans l'entrée du hook ; il refuse ainsi un changement vers Opus 4.6 en se terminant avec le code 2 et laisse passer toute autre cible :

La commande vérifie to_model avec jq :

{
"hooks": {
"PreModelSwitch": [
{
"matcher": "claude-opus-4-6",
"hooks": [
{
"type": "command",
"command": "jq -e '.to_model | test(\"opus-4-6\")' > /dev/null && { echo 'Opus 4.6 is retired for this project. Use a newer model.' >&2; exit 2; }; exit 0"
}
]
}
]
}
}

Pour vérifier que le hook fonctionne, exécutez /model claude-opus-4-6 depuis une session utilisant un autre modèle. Claude Code conserve le modèle actuel et indique qu'un hook PreModelSwitch a bloqué le changement, avec votre message comme motif.

Entrée de PreModelSwitch

En plus des champs d'entrée communs, les hooks PreModelSwitch reçoivent les champs de ce tableau. Les cinq derniers décrivent ce que coûte le renvoi de la conversation au nouveau modèle, afin qu'un hook puisse afficher ce montant avant que le changement n'ait lieu.

Champ Type Description
from_model string Identifiant du modèle que le changement quitte
to_model string Identifiant du modèle vers lequel le changement s'effectue. Le matcher est comparé au nom canonique de ce modèle
requested_model string ou null Le modèle indiqué dans la demande : un alias tel que opus, un identifiant de modèle complet, ou null lorsque la demande portait sur le modèle par défaut
source string Origine de la demande : "command" pour /model <name>, le paramètre Model dans /config ou l'activation du mode rapide ; "picker" pour un sélecteur de modèle ; "sdk" pour une requête set_model, ou un changement de modèle dans une requête apply_flag_settings, provenant d'un hôte Agent SDK ou de Remote Control
context_tokens number Tokens que la prochaine requête renvoie comme prompt : la somme des tokens d'entrée, de lecture du cache, de création du cache et de sortie de la dernière réponse de la conversation principale. 0 avant la première réponse
prompt_cache_warm boolean Indique si le cache de prompt du modèle actuel est probablement encore chaud, ce qui signifie que le changement le perd
cache_ttl string Durée de vie du cache de prompt que Claude Code demande pour cette session : "5m" ou "1h"
estimated_cache_write_usd number Coût estimé en dollars américains de l'écriture de context_tokens dans le cache de prompt sur to_model au tarif cache_ttl, hors réponse suivante. Le serveur n'a pas forcément besoin de remettre en cache tout le contexte ; considérez donc cette valeur comme une estimation
pricing string Façon dont Claude Code a calculé le prix de estimated_cache_write_usd : "configured" aux tarifs propres à votre organisation lorsqu'elle les a configurés, "catalog" au prix catalogue, ou "default" lorsque to_model n'a pas de prix connu et que Claude Code a supposé un tarif par défaut

Cet exemple montre l'entrée pour /model opus dans une session utilisant Sonnet 5 :

{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "cwd": "/Users/...",
  "hook_event_name": "PreModelSwitch",
  "from_model": "claude-sonnet-5",
  "to_model": "claude-opus-5",
  "requested_model": "opus",
  "source": "command",
  "context_tokens": 182340,
  "prompt_cache_warm": true,
  "cache_ttl": "5m",
  "estimated_cache_write_usd": 1.1396,
  "pricing": "catalog"
}

Contrôle de décision de PreModelSwitch

Les hooks PreModelSwitch peuvent annuler le changement, demander à l'utilisateur de le confirmer ou le laisser se poursuivre. Le code de sortie 2 ou un decision: "block" de premier niveau annule le changement.

Pour un contrôle plus fin, renvoyez permissionDecision et permissionDecisionReason dans un objet hookSpecificOutput, comme pour PreToolUse. PreModelSwitch accepte "allow", "deny" et "ask". Il n'accepte pas "defer", updatedInput ni additionalContext. Le tableau ci-dessous décrit les deux champs :

Champ Description
permissionDecision "allow" poursuit et ignore la confirmation que Claude Code affiche tant que le cache de prompt est chaud. "deny" annule le changement. "ask" demande à l'utilisateur de le confirmer
permissionDecisionReason Pour "deny", affiché à l'utilisateur comme motif du blocage du changement, ou renvoyé comme erreur pour une requête set_model. Pour "ask", affiché dans la demande de confirmation. Ignoré pour "allow"

Seul /model dans une session interactive peut afficher la demande "ask". Sur toutes les autres surfaces, y compris le mode non interactif avec le flag -p, /config et les requêtes set_model, Claude Code traite "ask" comme un refus.

Cet exemple demande une confirmation à l'utilisateur et cite le nombre de tokens issu de context_tokens :

{
  "hookSpecificOutput": {
    "hookEventName": "PreModelSwitch",
    "permissionDecision": "ask",
    "permissionDecisionReason": "Switching now re-sends about 180k tokens to the new model. Continue?"
  }
}

Lorsque plusieurs hooks PreModelSwitch renvoient des décisions différentes, l'ordre de priorité est deny > ask > allow.

Claude Code affiche à l'utilisateur tout systemMessage renvoyé par votre hook, quelle que soit la décision ; un hook de rapport de coût peut donc renvoyer {"systemMessage": "..."} et se terminer avec le code 0.

Un hook PreModelSwitch qui ne répond pas avant l'expiration de son délai bloque le changement. Pour PreToolUse, à l'inverse, un hook de commande ayant expiré laisse l'appel d'outil se poursuivre. Le délai d'expiration par défaut pour cet événement est de 30 secondes. PreModelSwitch exécute uniquement les hooks command, http et mcp_tool ; les valeurs par défaut de prompt et agent ne s'appliquent donc pas.

Un hook qui se termine avec un code autre que 0 ou 2 et n'affiche aucune décision JSON ne bloque pas : Claude Code affiche son stderr et applique le changement, comme décrit dans Autres codes de sortie.

PostModelSwitch

S'exécute après le changement du modèle de la session. Utilisez-le pour donner à Claude des consignes propres à un modèle sans modifier chaque CLAUDE.md, par exemple une instruction à l'échelle de l'organisation qui s'applique à certains modèles.

PostModelSwitch nécessite Claude Code v2.1.251 ou une version ultérieure. Il ne peut pas bloquer, car le modèle a déjà changé. Claude Code exécute les hooks PostModelSwitch après l'un de ces changements :

Claude Code n'exécute pas les hooks PostModelSwitch lorsqu'un modèle d'une chaîne de modèles de secours traite un tour, car cette substitution dure un seul tour et ne modifie pas le modèle de la session.

Le matcher suit les mêmes règles que pour PreModelSwitch : Claude Code le compare au nom canonique du modèle vers lequel la session a basculé.

Cet exemple ajoute des consignes chaque fois que le modèle de la session passe à un modèle Opus :

{
  "hooks": {
    "PostModelSwitch": [
      {
        "matcher": ".*opus.*",
        "hooks": [
          {
            "type": "command",
            "command": "echo 'On Opus, delegate implementation work to subagents and keep this conversation for planning and review.'"
          }
        ]
      }
    ]
  }
}

Pour vérifier que le hook fonctionne, passez à un modèle Opus depuis une session utilisant un autre modèle, par exemple en exécutant /model opus depuis une session Sonnet, puis demandez à Claude quelles consignes il a concernant le modèle actuel.

Entrée de PostModelSwitch

Les hooks PostModelSwitch reçoivent les mêmes champs que PreModelSwitch, avec hook_event_name défini sur "PostModelSwitch" et deux valeurs supplémentaires pour source : "auto" pour un basculement automatique vers un modèle de secours ou tout autre changement effectué par Claude Code de lui-même, et "resume" pour le modèle restauré lorsque vous reprenez une session.

requested_model vaut null lorsque source vaut "auto". Lorsque source vaut "resume", il s'agit du paramètre de modèle enregistré que Claude Code a restauré.

Contrôle de décision de PostModelSwitch

Claude Code récupère le stdout en texte brut de votre hook lors d'une sortie avec le code 0, ou additionalContext depuis la sortie JSON, et le transmet à Claude avec la prochaine requête après le changement. En plus des champs de sortie JSON disponibles pour tous les hooks, vous pouvez renvoyer :

Champ Description
additionalContext Chaîne ajoutée au contexte de Claude avec la prochaine requête. Consultez Ajouter du contexte pour Claude

Si le hook n'a pas terminé dans les cinq secondes suivant l'envoi de votre prochain prompt, Claude Code envoie cette requête sans la sortie et la joint plutôt à la requête suivante. Si le modèle change plusieurs fois avant la prochaine requête, Claude Code transmet uniquement la sortie correspondant au modèle cible du dernier changement.

SessionEnd

S'exécute lorsqu'une session Claude Code se termine. Utile pour les tâches de nettoyage, la journalisation des statistiques de session ou l'enregistrement de l'état de la session. Prend en charge les matchers pour filtrer selon le motif de sortie.

Le champ reason dans l'entrée du hook indique pourquoi la session s'est terminée :

Motif Description
clear Session effacée avec la commande /clear
resume Session changée via /resume en mode interactif
logout L'utilisateur s'est déconnecté
prompt_input_exit L'utilisateur a quitté alors que la saisie du prompt était visible
other Autres motifs de sortie
bypass_permissions_disabled Supprimé dans la v2.1.234 ; Claude Code ne l'envoie pas. Retirez-le de vos matchers SessionEnd

Entrée de SessionEnd

En plus des champs d'entrée communs, les hooks SessionEnd reçoivent un champ reason indiquant pourquoi la session s'est terminée. Consultez le tableau des motifs ci-dessus pour toutes les valeurs.

{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "cwd": "/Users/...",
  "hook_event_name": "SessionEnd",
  "reason": "other"
}

Les hooks SessionEnd n'ont aucun contrôle de décision. Ils ne peuvent pas bloquer la fin de la session, mais peuvent effectuer des tâches de nettoyage. Claude Code ignore leurs champs de sortie JSON, tels que systemMessage.

Les hooks SessionEnd ont un délai d'expiration par défaut de 1,5 seconde. Il s'applique lorsque vous quittez, exécutez /clear ou changez de session avec /resume en mode interactif. Vous pouvez accorder plus de temps à un hook de deux façons :

  • timeout par hook : définissez timeout dans la configuration de ce hook. Le budget global augmente automatiquement pour correspondre au timeout par hook le plus élevé de vos fichiers de paramètres, jusqu'à 60 secondes. Si vous augmentez le budget de cette façon, un hook sans son propre timeout conserve tout de même la valeur par défaut. Les délais d'expiration définis sur des hooks fournis par des plugins n'augmentent pas le budget.
  • CLAUDE_CODE_SESSIONEND_HOOKS_TIMEOUT_MS : définissez cette variable d'environnement en millisecondes pour remplacer explicitement le budget. La valeur que vous définissez devient également le délai d'expiration de chaque hook sans son propre timeout.

Cet exemple définit le budget à 5 secondes :

CLAUDE_CODE_SESSIONEND_HOOKS_TIMEOUT_MS=5000 claude

Avant la v2.1.268, CLAUDE_CODE_SESSIONEND_HOOKS_TIMEOUT_MS augmentait uniquement le budget global, et un hook sans son propre timeout était tout de même annulé au bout de 1,5 seconde.

Elicitation

S'exécute lorsqu'un serveur MCP demande une saisie de l'utilisateur en cours de tâche. Par défaut, Claude Code affiche une boîte de dialogue interactive pour que l'utilisateur réponde. Les hooks peuvent intercepter cette demande et y répondre de manière programmatique, en ignorant entièrement la boîte de dialogue.

Le champ matcher est comparé au nom du serveur MCP.

Entrée d'Elicitation

En plus des champs d'entrée communs, les hooks Elicitation reçoivent mcp_server_name, message et les champs facultatifs mode, url, elicitation_id et requested_schema.

Pour l'élicitation en mode formulaire, le cas le plus courant :

{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "cwd": "/Users/...",
  "hook_event_name": "Elicitation",
  "mcp_server_name": "my-mcp-server",
  "message": "Please provide your credentials",
  "mode": "form",
  "requested_schema": {
    "type": "object",
    "properties": {
      "username": { "type": "string", "title": "Username" }
    }
  }
}

Pour l'élicitation en mode URL, utilisée pour l'authentification via le navigateur :

{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "cwd": "/Users/...",
  "hook_event_name": "Elicitation",
  "mcp_server_name": "my-mcp-server",
  "message": "Please authenticate",
  "mode": "url",
  "url": "https://auth.example.com/login"
}

Sortie d'Elicitation

Pour répondre de manière programmatique sans afficher la boîte de dialogue, renvoyez un objet JSON avec hookSpecificOutput :

{
  "hookSpecificOutput": {
    "hookEventName": "Elicitation",
    "action": "accept",
    "content": {
      "username": "alice"
    }
  }
}
Champ Valeurs Description
action accept, decline, cancel Indique s'il faut accepter, refuser ou annuler la demande
content object Valeurs des champs du formulaire à soumettre. Utilisé uniquement lorsque action vaut accept

Le code de sortie 2 refuse l'élicitation. Claude Code n'affiche votre message stderr nulle part.

Claude Code tient compte de hookSpecificOutput dans la sortie JSON d'un hook Elicitation et ignore systemMessage et continue.

ElicitationResult

S'exécute après qu'un utilisateur a répondu à une élicitation MCP. Les hooks peuvent observer, modifier ou bloquer la réponse avant qu'elle ne soit renvoyée au serveur MCP.

Le champ matcher est comparé au nom du serveur MCP.

Entrée d'ElicitationResult

En plus des champs d'entrée communs, les hooks ElicitationResult reçoivent mcp_server_name, action et les champs facultatifs mode, elicitation_id et content.

{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "cwd": "/Users/...",
  "hook_event_name": "ElicitationResult",
  "mcp_server_name": "my-mcp-server",
  "action": "accept",
  "content": { "username": "alice" },
  "mode": "form",
  "elicitation_id": "elicit-123"
}

Sortie d'ElicitationResult

Pour remplacer la réponse de l'utilisateur, renvoyez un objet JSON avec hookSpecificOutput :

{
  "hookSpecificOutput": {
    "hookEventName": "ElicitationResult",
    "action": "decline",
    "content": {}
  }
}
Champ Valeurs Description
action accept, decline, cancel Remplace l'action de l'utilisateur
content object Remplace les valeurs des champs du formulaire. N'a de sens que lorsque action vaut accept

Le code de sortie 2 bloque la réponse, en changeant l'action effective en decline. Claude Code n'affiche votre message stderr nulle part.

Claude Code tient compte de hookSpecificOutput dans la sortie JSON d'un hook ElicitationResult et ignore systemMessage et continue.

Hooks basés sur des prompts

En plus des hooks de commande, HTTP et MCP tool, Claude Code supporte les hooks basés sur des prompts (type: "prompt") qui utilisent un LLM pour évaluer s'il faut autoriser ou bloquer une action, et les hooks d'agent (type: "agent") qui lancent un vérificateur agentique avec accès aux outils. Tous les événements ne supportent pas tous les types de hooks.

Les événements qui supportent les cinq types de hooks (command, http, mcp_tool, prompt et agent) :

  • PermissionDenied
  • PostToolBatch
  • PostToolUse
  • PostToolUseFailure
  • PreToolUse
  • Stop
  • SubagentStop
  • TaskCompleted
  • TaskCreated
  • TeammateIdle
  • UserPromptExpansion
  • UserPromptSubmit

PermissionRequest supporte les hooks command, http, mcp_tool et prompt mais pas les hooks agent. Si vous configurez un hook d'agent sur cet événement, Claude Code le saute et le flux de permission se poursuit sans changement. Pour autoriser ou refuser à partir d'un hook, retournez l'objet de décision à partir d'un hook de commande ou HTTP.

Les événements qui supportent les hooks command, http et mcp_tool mais pas prompt ou agent :

  • ConfigChange
  • CwdChanged
  • DirectoryAdded
  • Elicitation
  • ElicitationResult
  • FileChanged
  • InstructionsLoaded
  • MessageDisplay
  • Notification
  • PostCompact
  • PostModelSwitch
  • PreCompact
  • PreModelSwitch
  • SessionEnd
  • StopFailure
  • SubagentStart
  • WorktreeCreate
  • WorktreeRemove

SessionStart et Setup supportent les hooks command et mcp_tool, et les champs des hooks MCP tool décrivent quand leurs hooks mcp_tool s'exécutent. Ils ne supportent pas les hooks http, prompt ou agent.

Comment fonctionnent les hooks basés sur des prompts

Au lieu d'exécuter une commande Bash, les hooks basés sur des prompts :

  1. Envoient l'entrée du hook et votre prompt à un modèle Claude, par défaut celui que Claude Code utilise pour la fonctionnalité en arrière-plan
  2. Le LLM répond avec JSON structuré contenant une décision
  3. Claude Code traite automatiquement la décision

Configuration des hooks de prompt

Définissez type à "prompt" et fournissez une chaîne prompt au lieu d'une command. Utilisez le placeholder $ARGUMENTS pour injecter les données d'entrée JSON du hook dans votre texte de prompt.

Ce hook Stop demande au LLM d'évaluer si toutes les tâches sont complètes avant d'autoriser Claude à terminer :

{
  "hooks": {
    "Stop": [
      {
        "hooks": [
          {
            "type": "prompt",
            "prompt": "Evaluate if Claude should stop: $ARGUMENTS. Check if all tasks are complete."
          }
        ]
      }
    ]
  }
}
Champ Requis Description
type oui Doit être "prompt"
prompt oui Le texte du prompt à envoyer au LLM. Utilisez $ARGUMENTS comme placeholder pour l'entrée JSON du hook. Si $ARGUMENTS n'est pas présent, l'entrée JSON est ajoutée au prompt
model non Modèle à utiliser pour l'évaluation. Par défaut le modèle que Claude Code utilise pour la fonctionnalité en arrière-plan
timeout non Délai d'expiration en secondes. Par défaut : 30
continueOnBlock non Sur les événements auxquels elle s'applique, true renvoie une raison ok: false à Claude et continue au lieu de terminer le tour. Par défaut : false. Voir Schéma de réponse pour le comportement par événement

Schéma de réponse

Le LLM doit répondre avec JSON contenant :

{
  "ok": true | false,
  "reason": "Explanation for the decision",
  "impossible": true | false
}
Champ Description
ok true pour autoriser. Pour false, voir le comportement par événement ci-dessous
reason Requis lorsque ok est false
impossible Optionnel. Le modèle le retourne avec ok: false lorsqu'il juge que la condition ne peut jamais être satisfaite. Sur Stop et SubagentStop, Claude Code laisse alors le tour se terminer au lieu de renvoyer la raison. Les hooks d'agent et les autres événements l'ignorent

Ce qui se passe sur ok: false dépend de l'événement :

  • Stop et SubagentStop : la raison est renvoyée à Claude comme sa prochaine instruction et le tour continue, sauf si la réponse définit également impossible: true, auquel cas Claude Code autorise l'arrêt et le tour se termine
  • PreToolUse : l'appel d'outil est refusé ; par défaut le tour se termine et la raison de refus apparaît dans le chat comme une ligne d'avertissement. Définissez continueOnBlock: true pour renvoyer la raison à Claude comme l'erreur de l'outil afin qu'il puisse s'ajuster et continuer, équivalent à un hook de commande avec permissionDecision: "deny". Avant v2.1.210, la raison de refus était renvoyée à Claude comme l'erreur de l'outil et le tour continuait
  • PostToolUse : par défaut le tour se termine et la raison apparaît dans le chat comme une ligne d'avertissement. Définissez continueOnBlock: true pour renvoyer la raison à Claude et continuer le tour à la place
  • PostToolBatch, UserPromptSubmit et UserPromptExpansion : le tour se termine et la raison apparaît comme une ligne d'avertissement. Ces événements terminent le tour sur decision: "block" indépendamment de continue
  • PostToolUseFailure et TaskCreated : la raison est retournée à Claude comme une erreur d'outil et le tour continue, indépendamment de continueOnBlock
  • TaskCompleted : lorsqu'il se déclenche parce qu'une tâche est marquée comme complétée pendant un tour, la raison est retournée à Claude comme une erreur d'outil et le tour continue, indépendamment de continueOnBlock. Lorsqu'il se déclenche parce qu'un coéquipier s'arrête, il se comporte comme TeammateIdle et arrête le coéquipier par défaut
  • TeammateIdle : par défaut le coéquipier s'arrête et la raison apparaît comme une ligne d'avertissement. Définissez continueOnBlock: true pour renvoyer la raison au coéquipier et le garder actif à la place
  • PermissionRequest : ok: false n'a aucun effet. Pour refuser une approbation d'un hook, utilisez un hook de commande retournant hookSpecificOutput.decision.behavior: "deny"
  • PermissionDenied : ok: false n'a aucun effet car le refus a déjà eu lieu. La seule sortie que cet événement lit est hookSpecificOutput.retry, que les hooks de prompt et d'agent ne peuvent pas définir. Ils s'exécutent sur cet événement, mais leur sortie est ignorée. Utilisez un hook de commande pour retourner retry

Si vous avez besoin d'un contrôle plus fin sur un événement quelconque, utilisez un hook de commande avec les champs par événement décrits dans Contrôle de décision.

Vérifier plusieurs conditions avant d'arrêter

Ce hook Stop utilise un prompt détaillé pour vérifier trois conditions avant d'autoriser Claude à s'arrêter. Les hooks SubagentStop utilisent le même format pour évaluer si un subagent doit s'arrêter. Si le modèle retourne "ok": false parce que la condition n'est pas encore satisfaite, Claude continue de travailler avec la raison fournie comme sa prochaine instruction :

{
  "hooks": {
    "Stop": [
      {
        "hooks": [
          {
            "type": "prompt",
            "prompt": "You are evaluating whether Claude should stop working. Context: $ARGUMENTS\n\nAnalyze the conversation and determine if:\n1. All user-requested tasks are complete\n2. Any errors need to be addressed\n3. Follow-up work is needed\n\nRespond with JSON: {\"ok\": true} to allow stopping, or {\"ok\": false, \"reason\": \"your explanation\"} to continue working.",
            "timeout": 30
          }
        ]
      }
    ]
  }
}

Hooks basés sur des agents

Les hooks basés sur des agents (type: "agent") sont comme les hooks basés sur des prompts mais avec accès aux outils multi-tours. Au lieu d'un seul appel LLM, un hook d'agent lance un subagent qui peut lire des fichiers, rechercher du code et inspecter la codebase pour vérifier les conditions. Les hooks d'agent supportent les mêmes événements que les hooks basés sur des prompts, sauf PermissionRequest.

Comment fonctionnent les hooks d'agent

Lorsqu'un hook d'agent se déclenche :

  1. Claude Code lance un subagent avec votre prompt et l'entrée JSON du hook
  2. Le subagent peut utiliser des outils comme Read, Grep et Glob pour enquêter
  3. Après jusqu'à 50 tours, le subagent retourne une décision structurée { "ok": true/false }
  4. Claude Code autorise l'action si ok est true. Si ok est false, Claude Code traite le blocage de la même manière qu'un hook de prompt avec continueOnBlock: true sur cet événement, comme indiqué sous Schéma de réponse

Les hooks d'agent sont utiles lorsque la vérification nécessite d'inspecter les fichiers réels ou la sortie des tests, pas seulement d'évaluer les données d'entrée du hook seules.

Configuration des hooks d'agent

Définissez type à "agent" et fournissez une chaîne prompt, en utilisant $ARGUMENTS comme placeholder pour l'entrée JSON du hook. Les champs de configuration sont les mêmes que les hooks de prompt, sauf que les hooks d'agent ont un délai d'expiration par défaut plus long de 60 secondes et aucun champ continueOnBlock.

Le schéma de réponse est { "ok": true } pour autoriser ou { "ok": false, "reason": "..." } pour bloquer. Sur ok: false, Claude Code traite un hook d'agent de la même manière qu'il traite un hook de prompt avec continueOnBlock: true sur le même événement ; les hooks d'agent n'ont pas de champ continueOnBlock et ne supportent pas le champ impossible du hook de prompt.

Ce hook Stop vérifie que tous les tests unitaires réussissent avant d'autoriser Claude à terminer :

{
  "hooks": {
    "Stop": [
      {
        "hooks": [
          {
            "type": "agent",
            "prompt": "Verify that all unit tests pass. Run the test suite and check the results. $ARGUMENTS",
            "timeout": 120
          }
        ]
      }
    ]
  }
}

Exécuter les hooks en arrière-plan

Par défaut, les hooks bloquent l'exécution de Claude jusqu'à ce qu'ils se terminent. Pour les tâches longues comme les déploiements, les suites de tests ou les appels API externes, définissez "async": true pour exécuter le hook en arrière-plan tandis que Claude continue de travailler. Les hooks asynchrones ne peuvent pas bloquer ou contrôler le comportement de Claude : les champs de réponse comme decision, permissionDecision et continue n'ont aucun effet, car l'action qu'ils auraient contrôlée s'est déjà produite.

Configurer un hook asynchrone

Ajoutez "async": true à la configuration d'un hook de commande pour l'exécuter en arrière-plan sans bloquer Claude. Ce champ n'est disponible que sur les hooks type: "command".

Ce hook exécute un script de test après chaque appel d'outil Write. Claude continue de travailler immédiatement tandis que run-tests.sh s'exécute. Lorsque le script se termine, sa sortie est livrée au tour de conversation suivant :

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write",
        "hooks": [
          {
            "type": "command",
            "command": "/path/to/run-tests.sh",
            "async": true
          }
        ]
      }
    ]
  }
}

Une fois qu'un hook asynchrone s'exécute en arrière-plan, Claude Code n'applique pas de timeout sur celui-ci. Claude Code applique toujours le timeout sur un hook que vous exécutez avec asyncRewake.

Claude Code livre les résultats d'un hook asynchrone uniquement pendant que la session s'exécute :

  • En mode non-interactif avec le drapeau -p, Claude Code tue tout hook asynchrone encore en cours d'exécution lors du démontage et le finalise avec le résultat cancelled
  • Si le travail de votre hook doit survivre à une session claude -p, démarrez un processus complètement détaché à partir de celui-ci

Comment les hooks asynchrones s'exécutent

Lorsqu'un hook asynchrone se déclenche, Claude Code démarre le processus du hook et continue immédiatement sans attendre qu'il se termine. Le hook reçoit la même entrée JSON via stdin qu'un hook synchrone.

Après la sortie du processus en arrière-plan, Claude Code livre les champs additionalContext et systemMessage de la réponse JSON du hook à Claude au tour de conversation suivant. Contrairement au systemMessage d'un hook synchrone, aucun de ces champs ne vous est montré.

Claude Code valide que la réponse JSON respecte le même schéma de sortie que les hooks synchrones, et supprime tout champ dont la valeur a le mauvais type, comme un systemMessage qui n'est pas une chaîne de caractères, au lieu de le livrer. Exécutez avec --debug pour voir un avertissement nommant chaque champ supprimé. Avant la v2.1.202, une sortie JSON malformée d'un hook asynchrone pouvait faire planter la session, et le plantage s'est reproduit chaque fois que la session a été reprise.

Les notifications d'achèvement des hooks asynchrones sont supprimées par défaut. Pour les voir, activez le mode verbeux avec Ctrl+O ou démarrez Claude Code avec --verbose.

Exécuter les tests après les modifications de fichiers

Ce hook démarre une suite de tests en arrière-plan chaque fois que Claude écrit un fichier, puis rapporte les résultats à Claude lorsque les tests se terminent. Enregistrez ce script dans .claude/hooks/run-tests-async.sh dans votre projet et rendez-le exécutable avec chmod +x :

#!/bin/bash
# run-tests-async.sh

# Lisez l'entrée du hook depuis stdin
INPUT=$(cat)
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')

# Exécutez les tests uniquement pour les fichiers source
if [[ "$FILE_PATH" != *.ts && "$FILE_PATH" != *.js ]]; then
  exit 0
fi

# Exécutez les tests et rapportez les résultats à Claude via additionalContext
RESULT=$(npm test 2>&1)
EXIT_CODE=$?

if [ $EXIT_CODE -eq 0 ]; then
  MSG="Tests passed after editing $FILE_PATH"
else
  MSG="Tests failed after editing $FILE_PATH: $RESULT"
fi
jq -nc --arg msg "$MSG" '{hookSpecificOutput: {hookEventName: "PostToolUse", additionalContext: $msg}}'

Ensuite, ajoutez cette configuration à .claude/settings.json dans la racine de votre projet. Le drapeau async: true permet à Claude de continuer à travailler pendant que les tests s'exécutent :

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "command",
            "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/run-tests-async.sh",
            "args": [],
            "async": true
          }
        ]
      }
    ]
  }
}

Limitations

Les hooks asynchrones ont des contraintes supplémentaires par rapport aux hooks synchrones :

  • La sortie du hook est livrée au tour de conversation suivant. Si la session est inactive, la réponse attend jusqu'à la prochaine interaction utilisateur. Exception : un hook asyncRewake qui quitte avec le code 2 réveille Claude immédiatement même lorsque la session est inactive.
  • Chaque exécution crée un processus en arrière-plan séparé. Il n'y a pas de déduplication sur plusieurs déclenchements du même hook asynchrone.

Considérations de sécurité

Avertissement

Confiance de l'espace de travail

Claude Code vérifie la confiance de l'espace de travail avant d'exécuter tout hook à partir d'un fichier de paramètres. Ce qui compte comme approuvé dépend du type de session :

  • Session interactive : Claude Code retient les hooks de tous les fichiers de paramètres, y compris votre propre ~/.claude/settings.json, jusqu'à ce que vous acceptiez le dialogue de confiance de l'espace de travail pour le dossier, ou pour un répertoire parent dont la confiance s'étend à celui-ci
  • Session -p ou SDK : Claude Code n'affiche jamais le dialogue et traite le dossier comme approuvé, donc les hooks validés dans le .claude/settings.json d'un référentiel s'exécutent dans un dossier que vous n'avez jamais approuvé

Avant de scripter claude -p sur un référentiel que vous n'avez pas écrit, examinez ses fichiers de paramètres .claude/, commencez par --bare, ou désactivez les hooks pour cette exécution avec --settings '{"disableAllHooks": true}'. Les hooks de frontmatter dans un sous-agent de projet suivent une règle plus stricte que les hooks de fichier de paramètres. Ce qui s'exécute avant que vous approuviez un dossier énumère chaque type de contenu de référentiel par type de session.

Meilleures pratiques de sécurité

Gardez ces pratiques à l'esprit lors de l'écriture de hooks :

  • Validez et nettoyez les entrées : ne faites jamais confiance aux données d'entrée aveuglément
  • Citez toujours les variables shell : utilisez "$VAR" pas $VAR
  • Bloquez la traversée de répertoires : vérifiez les .. dans les chemins de fichiers
  • Utilisez les chemins absolus : spécifiez les chemins complets pour les scripts. En forme exec, utilisez ${CLAUDE_PROJECT_DIR} et le chemin n'a pas besoin de guillemets. En forme shell, enveloppez-le dans des guillemets doubles
  • Ignorez les fichiers sensibles : évitez .env, .git/, les clés, etc.

Outil PowerShell sur Windows

Sur Windows, vous pouvez exécuter les hooks individuels dans PowerShell en définissant "shell": "powershell" sur un hook de commande. Claude Code détecte automatiquement pwsh.exe, l'exécutable PowerShell 7 et versions ultérieures, et bascule vers powershell.exe pour Windows PowerShell 5.1.

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write",
        "hooks": [
          {
            "type": "command",
            "shell": "powershell",
            "command": "Write-Host 'File written'"
          }
        ]
      }
    ]
  }
}

Pour référencer la racine du projet à partir d'une commande PowerShell en forme shell, écrivez ${CLAUDE_PROJECT_DIR} ou $env:CLAUDE_PROJECT_DIR. Claude Code réécrit les placeholders ${CLAUDE_PROJECT_DIR}, ${CLAUDE_PLUGIN_ROOT} et ${CLAUDE_PLUGIN_DATA} dans une commande PowerShell en forme shell vers la forme ${env:NAME} de PowerShell, que le hook soit défini dans settings.json, un plugin ou un skill. PowerShell résout ensuite la valeur à partir de l'environnement exporté après l'analyse, donc le placeholder fonctionne à l'intérieur des chaînes entre guillemets doubles mais pas à l'intérieur des chaînes entre guillemets simples, où PowerShell n'étend jamais les variables.

N'écrivez pas l'orthographe nue $CLAUDE_PROJECT_DIR dans un hook PowerShell. PowerShell l'analyse comme une variable locale indéfinie et la résout en $null, ce qui laisse le chemin du script sans son préfixe de racine de projet. Claude Code ne réécrit pas cette forme ; il enregistre plutôt un avertissement dans le journal de débogage.

L'exemple ci-dessous montre un hook settings.json qui exécute un script de projet avec la forme $env: :

{
  "type": "command",
  "shell": "powershell",
  "command": "& \"$env:CLAUDE_PROJECT_DIR\\.claude\\hooks\\check.ps1\""
}

Déboguer les hooks

Les détails d'exécution des hooks sont écrits dans le fichier journal de débogage. Démarrez Claude Code avec claude --debug-file <path> pour écrire le journal à un emplacement connu, ou exécutez claude --debug et lisez le journal à ~/.claude/debug/<session-id>.txt. Le drapeau --debug n'imprime pas sur le terminal.

Par exemple, un hook PostToolUse sur Write dont la commande affiche hook-ran produit des entrées comme :

2026-07-19T02:03:24.382Z [DEBUG] Hook output does not start with {, treating as plain text
2026-07-19T02:03:24.382Z [DEBUG] "Hook PostToolUse:Write (PostToolUse) success:\nhook-ran"

Pour plus de détails granulaires sur la correspondance des hooks, définissez CLAUDE_CODE_DEBUG_LOG_LEVEL=verbose pour voir des lignes de journal supplémentaires telles que les comptes de matcher de hook et la correspondance de requête.

Pour dépanner les problèmes courants comme les hooks qui ne se déclenchent pas, les hooks Stop qui continuent à bloquer, ou les erreurs de configuration, consultez Limitations et dépannage dans le guide. Pour une procédure de diagnostic plus large couvrant /context, /doctor et la précédence des paramètres, consultez Déboguer votre configuration.