SpyBara
Go Premium

hooks.md 2026-11-09 23:02 UTC to 2026-11-10 21:57 UTC

This page contains 28 additions and 4 deletions.

2026
Mon 2 23:57 Tue 3 23:58 Fri 6 23:01 Sat 7 23:58 Mon 9 23:02 Tue 10 21:57 Wed 11 22:59

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.

[!TIP]

Pour un guide de démarrage rapide avec des exemples, consultez Automatiser les actions avec les hooks.

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 les sessions cloud. 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

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 créé par un hook WorktreeCreate est en cours de suppression
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.

[tab] macOS/Linux

Le matcher se limite aux appels d'outil Bash et aux commandes Monitor, 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.

[tab] Windows (PowerShell)

Le matcher Bash|PowerShell couvre l'outil PowerShell ainsi que Bash. Une seule règle if ne nomme qu'un seul outil, donc chaque outil obtient son propre gestionnaire : le premier se limite aux sous-commandes Bash correspondant à rm *, le second aux commandes PowerShell correspondant à Remove-Item *. Les deux exécutent le même script via powershell.exe :

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash|PowerShell",
        "hooks": [
          {
            "type": "command",
            "if": "Bash(rm *)",
            "command": "powershell.exe",
            "args": [
              "-NoProfile",
              "-ExecutionPolicy",
              "Bypass",
              "-File",
              "${CLAUDE_PROJECT_DIR}/.claude/hooks/block-rm.ps1"
            ]
          },
          {
            "type": "command",
            "if": "PowerShell(Remove-Item *)",
            "command": "powershell.exe",
            "args": [
              "-NoProfile",
              "-ExecutionPolicy",
              "Bypass",
              "-File",
              "${CLAUDE_PROJECT_DIR}/.claude/hooks/block-rm.ps1"
            ]
          }
        ]
      }
    ]
  }
}

Le drapeau -NoProfile ignore le chargement de votre profil PowerShell pour que le hook démarre rapidement, et -ExecutionPolicy Bypass permet à PowerShell d'exécuter le fichier de script local.

Le script lit l'entrée JSON depuis stdin, extrait la commande et retourne une permissionDecision de "deny" si elle contient rm -rf ou Remove-Item suivi de -Recurse. Enregistrez-le dans .claude/hooks/block-rm.ps1 dans votre projet :

# .claude/hooks/block-rm.ps1
$callInput = [Console]::In.ReadToEnd() | ConvertFrom-Json
$command = $callInput.tool_input.command

if ($command -match 'rm -rf|Remove-Item.*-Recurse') {
  @{
    hookSpecificOutput = @{
      hookEventName = "PreToolUse"
      permissionDecision = "deny"
      permissionDecisionReason = "Destructive command blocked by hook"
    }
  } | ConvertTo-Json
} else {
  exit 0  # no decision; normal permission flow applies
}

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.

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 matcher pour filtrer le moment où il se déclenche, par exemple « 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é.

[!NOTE]

Cette page utilise des termes spécifiques pour chaque niveau : événement de hook pour le point du cycle de vie, groupe de matcher pour le filtre et gestionnaire de hook pour la commande shell, le point de terminaison HTTP, l'outil MCP, le prompt ou l'agent qui s'exécute. « Hook » seul fait référence à la fonctionnalité générale.

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 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 à l'outil Bash et, sur PreToolUse et PermissionRequest, également aux commandes Monitor ; 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 dont le nom commence 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, auth_storage_failure, 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, plugin_notification
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"
          }
        ]
      }
    ]
  }
}

Correspondre aux commandes Monitor

Un hook PreToolUse ou PermissionRequest avec un matcher Bash s'exécute également sur les commandes shell que Claude lance avec l'outil Monitor, mais ses allow et updatedInput n'ont aucun effet sur elles. Nécessite Claude Code v2.1.314 ou ultérieur. Les matchers tels que Bash|PowerShell, ^Bash$ et Bash.* sont également des matchers Bash. Un hook avec un matcher Bash ne s'exécute pas pour une surveillance WebSocket, et un hook PostToolUse avec un tel matcher ne s'exécute pas pour Monitor.

Un hook qui s'exécute pour une commande Monitor peut la refuser ou faire en sorte que Claude Code vous demande votre avis à son sujet. Pour autoriser ou réécrire la commande, remplacez le matcher Bash par un matcher qui inclut Monitor :

  • Matcher qui inclut Monitor : avec un matcher tel que Bash|Monitor ou .*, ou sans matcher, le hook reçoit l'entrée propre à l'appel Monitor, et allow et updatedInput s'appliquent. Dans if, nommez Monitor, comme dans Monitor(tail *), qui correspond selon les règles que suit Bash(tail *). Si son if nomme Bash, le hook reçoit toujours l'entrée de l'appel Monitor, mais allow et updatedInput n'ont aucun effet
  • Matcher Bash : le hook reçoit un appel Bash. Dans if, nommez Bash, comme dans Bash(tail *). Le hook ne s'exécute pas si son if nomme Monitor

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.

Lorsqu'un événement se déclenche, ses hooks correspondants s'exécutent en parallèle. Si plus de 12 d'entre eux sont des hooks de prompt ou d'agent, 12 de ceux-ci s'exécutent simultanément et les autres démarrent à mesure que ceux en cours d'exécution se terminent. Le timeout de chaque hook en attente commence à courir lorsque ce hook démarre.

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 consigne un avertissement nommant le répertoire de secours dans le journal de débogage. Pour une session worktree que vous démarrez depuis l'application de bureau, consultez Ce que les worktrees partagent avec le checkout principal.

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 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
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/**)".

Comment les modèles if correspondent aux commandes Bash

Pour les modèles Bash dans le champ if, 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
onFailure non Indique si l'action qui a déclenché le hook se poursuit lorsque le hook échoue : "continue" ou "block". Consultez Bloquer l'action lorsqu'un hook échoue pour "block" et pour le cas où le champ n'est pas défini. Nécessite Claude Code v2.1.295 ou ultérieur
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.

[!NOTE]

Sur Windows, la forme exec nécessite que command se résolve en un véritable exécutable tel qu'un .exe. Les shims .cmd et .bat que npm, npx, eslint et d'autres outils installent dans node_modules/.bin ne sont pas des exécutables et ne peuvent pas être lancés sans un shell. Pour les exécuter en forme exec, invoquez le script sous-jacent avec node directement, par exemple "command": "node", "args": ["${CLAUDE_PLUGIN_ROOT}/node_modules/eslint/bin/eslint.js"]. Le modèle node plus chemin de script fonctionne sur chaque plateforme car node.exe est un vrai binaire. Pour exécuter un shim .cmd ou .bat par nom, utilisez la forme shell.

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.*}.

[!NOTE]

En forme exec, command est uniquement le nom ou le chemin de l'exécutable. Si command est un nom nu sans séparateur de chemin et contient des espaces aux côtés de args, Claude Code enregistre un avertissement car le lancement échouera : il n'y a pas d'exécutable nommé node script.js. Déplacez les tokens supplémentaires dans args. Les chemins absolus avec des espaces, tels que C:\Program Files\nodejs\node.exe, sont un seul exécutable valide et ne déclenchent pas l'avertissement.

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
onFailure non Ce qu'il advient de l'action lorsque le hook échoue : "continue", la valeur par défaut, ou "block". Consultez Bloquer l'action lorsqu'un hook échoue. Nécessite Claude Code v2.1.295 ou ultérieur

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.

Si une erreur de hook contient l'un de ces messages, Claude Code n'a pas pu lire la réponse de l'outil, donc le hook n'a rendu aucun verdict :

MCP tool "security_scan" on server "my_server" gave no answer that could be read, because a message from the server was too large, so this hook gave no verdict. If this keeps happening, the tool must send a smaller answer.
MCP tool "security_scan" on server "my_server" answered without its text, which happens when a result is too large, so this hook gave no verdict. If this keeps happening, the tool must send a smaller answer.

Sur PreToolUse, Claude Code refuse l'appel d'outil que le hook vérifiait. Sur les autres événements tels que UserPromptSubmit, PostToolUse et Stop, l'erreur est non-bloquante.

Si l'erreur continue d'apparaître, renvoyez moins de données depuis l'outil, ou modifiez le tool ou l'input du hook afin que la réponse soit plus petite.

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.

[!NOTE]

Les worktrees sont différents. Si Claude entre dans un worktree pendant la session, Claude Code garde ${CLAUDE_PROJECT_DIR} où il était et passe le chemin du worktree à vos hooks d'une manière différente :

  • ${CLAUDE_PROJECT_DIR} reste en place : il pointe toujours vers la racine du projet où la session a démarré, donc une commande comme ${CLAUDE_PROJECT_DIR}/.claude/hooks/check-style.sh exécute toujours le script dans le checkout principal.
  • cwd suit Claude : le champ cwd dans l'entrée JSON du hook est la racine du worktree après que Claude entre dans un worktree, et le nouveau répertoire après que Claude exécute cd. Lisez-le lorsqu'un hook a besoin de savoir dans quel répertoire Claude travaille.

Pour une session worktree que vous démarrez depuis l'application de bureau, consultez Ce que les worktrees partagent avec le checkout principal pour savoir vers où pointe ${CLAUDE_PROJECT_DIR}.

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.

[tab] Scripts de projet

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": []
          }
        ]
      }
    ]
  }
}

[tab] Scripts de plugin

Définissez les hooks de plugin dans hooks/hooks.json avec un champ description optionnel au niveau supérieur. Lorsqu'un plugin est activé, ses hooks fusionnent avec vos hooks utilisateur et projet.

Cet exemple exécute un script de formatage fourni avec le plugin :

{
  "description": "Automatic code formatting",
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "command",
            "command": "${CLAUDE_PLUGIN_ROOT}/scripts/format.sh",
            "args": [],
            "timeout": 30
          }
        ]
      }
    ]
  }
}

Consultez la référence des composants de plugin pour plus de détails sur la création de hooks de plugin.

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 sous-agent 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 sous-agents liste quelles portées sont exemptées.

Limites des hooks de skill

Lorsque les hooks de frontmatter d'un skill ne se déclenchent pas après que vous ou Claude avez invoqué le skill, exécutez /hooks dans cette session. S'il les liste, consultez Le hook ne se déclenche pas. S'il n'en liste aucun, le skill est peut-être soumis à un verrouillage réservé aux plugins ou a dépassé une limite de hooks, et s'exécute sans aucun de ses hooks de frontmatter. Vérifiez chaque cas :

  • Verrouillage réservé aux plugins : l'administrateur de votre organisation peut restreindre les hooks aux plugins et aux paramètres gérés avec le paramètre géré strictPluginOnlyCustomization. Un skill conserve ses hooks de frontmatter sous ce verrouillage uniquement s'il s'agit d'un skill de plugin, d'un skill fourni ou d'un skill situé dans le répertoire des paramètres gérés. Les hooks de vos propres fichiers de paramètres ne s'exécutent pas non plus.
    • Vérifier : tapez /status dans une session Claude Code. Si la ligne Setting sources inclut Enterprise managed settings, demandez à votre administrateur si strictPluginOnlyCustomization verrouille les hooks.
    • Corriger : déplacez le skill dans un plugin, car les skills de plugin conservent leurs hooks de frontmatter. Répertoriez le plugin dans une marketplace, puis installez-le depuis celle-ci. Si Claude Code refuse la marketplace avec is blocked by enterprise policy, demandez à votre administrateur d'autoriser cette marketplace.
  • Limite de hooks : dans Claude Code v2.1.304 ou ultérieur, chaque source de skills a sa propre limite par session. Votre projet, votre dossier de skills personnels et chaque plugin comptent comme des sources distinctes. Les skills d'une même source peuvent enregistrer au maximum 1 024 hooks. Les matchers de ces hooks peuvent totaliser au maximum 16 384 caractères. Un matcher avec plusieurs hooks en dessous compte une fois pour chaque hook.
    • Vérifier : reproduisez la session où le hook ne s'est pas déclenché, avec la journalisation de débogage activée :
      1. Démarrez une nouvelle session depuis votre shell avec claude --debug. Si vous ne démarrez pas vos sessions depuis un shell, tapez plutôt d'abord /debug dans la nouvelle session.
      2. Invoquez les mêmes skills dans le même ordre, y compris ceux que Claude a invoqués, car les deux totaux s'accumulent skill après skill. Demandez à Claude d'invoquer tout skill que vous ne pouvez pas taper comme commande.
      3. Recherchez registers none of its hooks dans le journal de débogage. Une ligne contenant ce texte nomme le skill qui a dépassé la limite.
    • Corriger : déclarez moins de hooks ou des matchers plus courts dans ce skill ou dans les autres skills de sa source, puis démarrez une nouvelle session. Si vous ne pouvez pas modifier ces skills, demandez cette modification à leur auteur. En attendant, démarrez une nouvelle session et invoquez le skill dont vous avez besoin avant tout autre skill de sa source.

Si aucun de ces cas ne correspond au vôtre, vérifiez si le frontmatter n'a pas pu être analysé.

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
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 dont le champ level est le niveau d'effort en vigueur lorsque le hook s'exécute, y compris le niveau d'un skill, d'un sous-agent ou de ultrathink : "low", "medium", "high", "xhigh" ou "max". Présent pour PreToolUse, PostToolUse, Stop, SubagentStop et les autres événements ayant un contexte d'utilisation d'outil, sur les modèles qui prennent en charge l'effort. Si le modèle ne prend pas en charge votre niveau, level est le niveau qui a été exécuté. Un niveau que vous avez modifié peut apparaître ici avant qu'il ne prenne effet. $CLAUDE_EFFORT contient le même niveau.
hook_event_name Nom de l'événement qui s'est déclenché

agent_id et agent_type indiquent à votre script dans quel agent un hook s'est déclenché, comme un sous-agent, un coéquipier en cours de processus ou l'agent que vous avez choisi avec --agent :

Champ Description
agent_id Identifiant unique du sous-agent ou du coéquipier en cours de processus dans lequel le hook se déclenche.
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 sous-agent. Pour les sous-agents, le type du sous-agent a priorité sur la valeur --agent de la session. Consultez SubagentStart pour les valeurs que les sous-agents personnalisés et fournis par un plugin rapportent et comment écrire un matcher contre un nom limité au 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. Dans une session qui reçoit la configuration HIPAA, Claude Code supprime également les identifiants Anthropic de l'environnement du hook.

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

Lorsque l'exécution de votre hook se termine, Claude Code utilise son code de sortie pour décider s'il doit poursuivre l'action qui l'a déclenché, comme un appel d'outil ou un prompt :

  • Succès : votre hook quitte avec 0. Claude Code applique tous les champs de sortie JSON que votre hook a imprimés, et l'action se poursuit sauf si ces champs la bloquent ou la refusent. Hooks qui démarrent un processus en arrière-plan couvre l'exception.
  • Erreur bloquante : votre hook quitte avec 2. Sur les événements qui peuvent bloquer, Claude Code arrête l'action.
  • Erreur non-bloquante : votre hook quitte avec tout autre code, ou échoue d'une autre manière, par exemple en ne démarrant pas ou en imprimant du JSON invalide. L'action se poursuit, sauf pour les hooks dont l'échec bloque l'action, par exemple un hook avec onFailure défini sur "block". Sur des événements tels que PreToolUse, vous voyez un avis <hook name> hook error dans la transcription.

Ce que votre hook imprime sur stdout peut changer le résultat. Par exemple, lorsqu'un hook PreToolUse quitte avec 1 mais imprime du JSON qui passe la validation, ce JSON peut quand même décider de ce qui se passe. Pour trouver le résultat de votre hook sur un événement tel que PreToolUse, faites correspondre ce qu'il a imprimé sur stdout dans la première colonne avec son code de sortie en haut :

Stdout Exit 0 Exit 2 Tout autre code de sortie
Objet JSON qui passe la validation du schéma Succès. Les champs s'appliquent Erreur bloquante. Claude Code lit toujours les champs, mais ils ne peuvent pas remplacer le blocage Succès. Claude Code ignore le code de sortie, et les champs seuls décident. Pour les hooks dont l'échec bloque l'action, l'exécution compte plutôt comme un échec
JSON qui ne peut pas être analysé ou échoue la validation du schéma Erreur non-bloquante. L'avis porte le message d'analyse ou de validation Erreur bloquante. Votre stderr est la raison Erreur non-bloquante. L'avis porte le message d'analyse ou de validation
Texte brut, ou rien Succès Erreur bloquante. Votre stderr est la raison Erreur non-bloquante. L'avis porte la première ligne de votre stderr

Certains événements ont leurs propres règles :

  • WorktreeCreate : tout code de sortie non-zéro fait échouer la création du worktree, quoi que dise votre JSON.
  • WorktreeRemove : tout code de sortie non-zéro fait échouer la suppression du worktree si le répertoire existe toujours après.
  • Stop, SubagentStop, TaskCompleted et le hook UserPromptSubmit d'un plugin : lorsque votre hook quitte avec 2 sans rien sur stdout et que son stderr indique qu'un fichier est manquant, comme No such file or directory, Claude Code traite l'exécution comme une erreur non-bloquante.
  • Elicitation et ElicitationResult : Claude Code applique votre hookSpecificOutput lorsque votre hook quitte avec 0, et l'ignore sur tout autre code de sortie.
  • Les événements qui rejettent la sortie du hook, comme StopFailure : Claude Code ignore votre JSON sur chaque code de sortie, à part les champs d'effet secondaire comme terminalSequence, qui se déclenchent toujours.

Pour vérifier ce que fait le code de sortie 2 sur votre événement, consultez Comportement du code de sortie 2 par événement. Pour vérifier quels champs de décision il honore, consultez Contrôle de décision.

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é. Lorsque votre hook quitte avec 0 mais imprime du JSON qui ne peut pas être analysé ou qui échoue la validation du schéma, l'action se poursuit sauf si le hook est l'un de ceux dont l'échec bloque l'action. Au lieu d'un succès avec vos champs appliqués, l'exécution est une erreur non-bloquante.

Pour corriger votre sortie, utilisez le message d'analyse ou de validation que porte l'avis <hook name> hook error. Pour un hook qui n'est pas async, votre stdout compte comme sortie JSON uniquement lorsque vous imprimez un seul objet JSON, sur une ou plusieurs lignes, entouré uniquement d'espaces blancs. Claude Code lit tout autre stdout comme du texte brut ou un échec d'analyse :

  • Une sortie qui ne commence pas par {, ou qui commence par { et ne se termine pas par } : texte brut. Un tableau JSON et une chaîne JSON entre guillemets sont du texte brut selon cette règle.
  • Deux lignes ou plus qui s'analysent chacune comme JSON seules, la première commençant par { et la dernière se terminant par } : texte brut lorsqu'aucune ligne n'est un objet de sortie JSON qui définit un champ. Lorsque l'une d'elles l'est, la sortie est un échec d'analyse ; imprimez donc cet objet sans rien d'autre sur stdout.
  • Tout autre contenu qui commence par { et se termine par } mais n'est pas du JSON valide : un échec d'analyse.

Pour la plupart des événements, Claude Code écrit stdout dans le journal de débogage et ne l'affiche pas dans la transcription. Sur ces événements, votre stdout parvient à Claude ou au résumé de compaction :

  • UserPromptSubmit, UserPromptExpansion, SessionStart et PostModelSwitch : Claude Code ajoute le stdout en texte brut comme contexte que Claude peut voir et sur lequel agir. Il n'ajoute pas le stdout qu'il n'a pas pu analyser.
  • PreCompact : votre stdout devient des instructions pour le résumé de compaction

Claude ne voit jamais le stderr d'un hook qui quitte avec 0. Pour le lire vous-même sur des événements tels que PreToolUse, 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 s'est déjà exécuté.

Exit code 2

Quittez avec le code 2 pour bloquer l'action. Sur les événements qui peuvent bloquer, Claude Code arrête l'action : un hook PreToolUse bloque l'appel d'outil, par exemple, et un hook UserPromptSubmit rejette le prompt.

Le message qui accompagne le blocage est le stderr de votre hook. Si votre hook a également imprimé du JSON qui prend une décision de blocage, Claude Code utilise la raison de cette décision à la place.

Exit 2 bloque même lorsque votre hook imprime du JSON :

  • JSON qui passe la validation du schéma : Claude Code lit toujours les champs de sortie JSON, mais ils ne peuvent pas remplacer le blocage. Même une permissionDecision de "allow" ne laisse pas passer l'action. Sur Elicitation et ElicitationResult, le hookSpecificOutput d'un hook exit-2 est ignoré.
  • JSON qui échoue la validation du schéma : le hook bloque toujours. Claude Code utilise votre stderr comme raison de blocage et consigne l'échec de validation dans le journal de débogage.

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

Avec ce script enregistré comme hook PreToolUse sur Bash, une commande qui commence par rm est bloquée, et Claude reçoit le stderr du hook comme erreur de l'outil, préfixé par le nom de l'événement, le nom de l'outil et la commande du hook :

PreToolUse:Bash hook error: [${CLAUDE_PROJECT_DIR}/.claude/hooks/no-rm.sh]: Blocked: rm commands are not allowed

Autres codes de sortie

Un avis <hook name> hook error avec Failed with non-blocking status code: signifie que le code de sortie de votre hook n'était ni 0 ni 2 et que son stdout était du texte brut ou vide. Sauf si le hook est l'un de ceux dont l'échec bloque l'action, il s'agit d'une erreur non-bloquante, donc votre hook n'a pas arrêté l'action. Pour capturer le stderr complet de votre hook plutôt que la première ligne affichée par l'avis, activez la journalisation de débogage.

Par exemple, lorsqu'un hook PreToolUse sur Bash imprime something broke sur stderr et quitte avec 1, l'avis PreToolUse:Bash hook error porte cette ligne :

Failed with non-blocking status code: something broke

Si l'avis porte le message No such file or directory du shell, comme dans la ligne suivante, le hook ne s'est jamais exécuté, par exemple parce que le chemin de son script n'existe pas :

Failed with non-blocking status code: /bin/sh: /path/to/hook.sh: No such file or directory

Si le chemin est erroné, corrigez-le là où le hook est configuré. Sous forme shell, lorsque le chemin du script n'existe pas ou n'est pas exécutable, le shell quitte avec un code comme 127 et l'avis porte le message de l'interpréteur. Sauf si le hook est l'un de ceux dont l'échec bloque l'action, un hook qui ne peut pas démarrer est également une erreur non-bloquante.

Lorsque vous configurez un hook de politique, surveillez cet avis à sa première exécution, car un chemin mal orthographié dans settings.json signifie que le hook ne s'exécute jamais. Pour bloquer l'action lorsque le hook ne peut pas démarrer, définissez onFailure: "block".

[!WARNING]

Exit code 1 sans sortie JSON valide est une erreur non-bloquante sauf si le hook est l'un de ceux dont l'échec bloque l'action, même si 1 est le code d'échec Unix conventionnel. Si votre hook est destiné à appliquer une politique, utilisez exit 2.

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, un hook command, http ou mcp_tool expiré ne bloque pas l'appel d'outil, sauf pour les hooks dont l'échec bloque l'action. L'appel continue via le flux de permission normal, donc ne comptez pas sur un hook bloqué pour agir comme un contrôle. Pour bloquer l'appel lorsqu'un hook command ou http expire, définissez onFailure: "block". Un hook de rappel Agent SDK qui dépasse son délai d'expiration bloque l'appel d'outil.

Hooks qui démarrent un processus en arrière-plan

Un hook peut démarrer un programme de longue durée, comme un serveur de développement, et le laisser s'exécuter pendant que la session se poursuit. Dans le script du hook ou la commande sous forme shell :

  • Exécutez le programme en arrière-plan
  • Redirigez son stdout et son stderr. Par exemple, la commande Bash npm run dev > dev-server.log 2>&1 & envoie les deux vers un fichier. Si vous ne les redirigez pas, un hook qui n'est pas async met jusqu'à 10 secondes à se terminer après la fin de son propre processus.

Si votre hook de commande prend sa décision dans un processus en arrière-plan puis se termine, un refus que ce processus imprime après que Claude Code a cessé de lire ne bloque pas l'appel d'outil. Pour un hook qui n'est pas async, Claude Code termine la lecture de la sortie du hook peu après la fin du propre processus du hook.

Imprimez la décision avant de démarrer un processus en arrière-plan, et redirigez le stdout et le stderr de ce processus. Si votre script imprime une autorisation, puis démarre un processus en arrière-plan sans rediriger le stdout et le stderr de ce processus, Claude Code bloque l'appel d'outil. L'erreur du hook inclut stdio went quiet et refusing to honor the suspect capture.

Ce script de hook PreToolUse imprime la décision avec policy-check.sh, puis envoie des métriques en arrière-plan :

#!/bin/bash
"$CLAUDE_PROJECT_DIR"/.claude/hooks/policy-check.sh || exit $?
"$CLAUDE_PROJECT_DIR"/.claude/hooks/upload-metrics.sh >/dev/null 2>&1 &

Le reste du script fait ceci :

  • || exit $? : fait du code de sortie de la vérification celui du hook, de sorte qu'une vérification qui quitte avec 2 pour refuser bloque toujours. Sinon, le hook quitte avec le code de la dernière ligne, qui est 0.
  • >/dev/null 2>&1 & : exécute upload-metrics.sh en arrière-plan avec sa sortie redirigée, afin qu'il ne garde pas la sortie du hook ouverte.

Pour tester le script, faites en sorte que policy-check.sh quitte avec 2, puis déclenchez un appel d'outil auquel le hook correspond. L'appel d'outil est bloqué.

Bloquer l'action lorsqu'un hook échoue

Sur la plupart des événements, lorsqu'un hook échoue ou expire, Claude Code exécute quand même l'action. Un hook de politique avec un chemin erroné ou un script qui plante laisse alors tout passer. Pour bloquer l'action à la place, définissez "onFailure": "block" sur un hook command ou http. Nécessite Claude Code v2.1.295 ou ultérieur. Pour les hooks de rappel Agent SDK, consultez Bloquer l'action lorsqu'un rappel échoue.

[!NOTE]

Sur Windows sans Git Bash, un hook de commande PreToolUse ou PermissionRequest sous forme shell qui échoue sans onFailure défini peut au contraire bloquer l'appel d'outil.

Ce hook PreToolUse dans .claude/settings.json exécute un script de projet avant chaque commande Bash, et bloque la commande si le script échoue :

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "node",
            "args": ["${CLAUDE_PROJECT_DIR}/.claude/hooks/check-command.js"],
            "onFailure": "block"
          }
        ]
      }
    ]
  }
}

Pour le tester, laissez check-command.js absent et demandez à Claude d'exécuter une commande Bash telle que ls. Claude Code bloque l'appel, et l'erreur inclut failed; blocking because onFailure is "block" suivi de la propre sortie d'erreur de node, réduite ici à une ligne :

PreToolUse:Bash hook error: [node ${CLAUDE_PROJECT_DIR}/.claude/hooks/check-command.js]: failed; blocking because onFailure is "block"
Error: Cannot find module '/path/to/project/.claude/hooks/check-command.js'

Après un délai d'expiration, le message indique timed out au lieu de failed. Sans onFailure défini, le même script manquant est une erreur non-bloquante et ls s'exécute.

Chacun de ces cas compte comme un échec :

  • Impossible de démarrer : un hook de commande ne parvient pas à démarrer, par exemple parce que le script ou l'exécutable n'existe pas
  • Code de sortie autre que 0 ou 2 : compte pour un hook de commande même s'il a imprimé du JSON qui autorise l'action, comme permissionDecision: "allow". Pour retourner une décision JSON, quittez avec 0
  • Erreur HTTP : la connexion d'un hook HTTP échoue, ou le statut de la réponse n'est pas 2xx
  • Délai d'expiration : le hook atteint son timeout
  • Sortie invalide : la sortie JSON ne peut pas être analysée ou échoue la validation du schéma. Pour un hook HTTP, un corps 2xx qui n'est ni vide ni un objet JSON compte également. Le stdout en texte brut d'un hook de commande n'est pas un échec

Avec "block" défini, un échec fait ce que le code de sortie 2 fait sur cet événement, sauf sur PermissionRequest, où il refuse la demande. Par exemple, un échec PreToolUse bloque l'appel d'outil et un échec UserPromptSubmit bloque le prompt.

Le champ n'a aucun effet sur ces hooks :

  • Hooks Stop, SubagentStop, TaskCompleted et TeammateIdle : le code de sortie 2 sur ces événements renvoie Claude au travail, et Claude ne peut pas réparer un hook qui ne s'exécute pas
  • Hooks de commande en arrière-plan : les hooks de commande qui définissent async ou asyncRewake

Échecs de hooks sur Windows sans Git Bash

Si l'erreur d'un appel d'outil bloqué inclut ce texte, un hook de commande PreToolUse ou PermissionRequest a échoué ou expiré et a bloqué l'appel :

blocking because Git Bash wasn't found, so only exit code 0 or 2 counts as this hook's verdict.

Si l'erreur indique également PowerShell started no program, commencez par les corrections pour une command composée uniquement de chaînes entre guillemets, que PowerShell imprime au lieu de les exécuter.

Sans Git Bash, tout code de sortie autre que 0 ou 2 de ce hook compte comme un échec, même lorsqu'il imprime du JSON qui ne refuse pas l'appel. Pour savoir pourquoi le hook a échoué, consultez le journal de débogage. Choisissez ensuite la correction qui correspond à votre hook :

  • L'appel doit se poursuivre lorsque le hook échoue : ajoutez "onFailure": "continue" au hook
  • Le hook quitte avec 1 pour avertir mais doit bloquer lorsqu'il plante : avec "onFailure": "continue", l'appel se poursuit sur le code de sortie 1, qu'il s'agisse d'un avertissement ou d'un plantage. Ajoutez plutôt "onFailure": "block", et terminez le script avec le code de sortie 0 là où il avertit, afin que ces appels se poursuivent. Pour afficher quand même l'avertissement sur PreToolUse, imprimez-le sur stdout dans le champ systemMessage d'un seul objet JSON.
  • Le hook est écrit pour Bash : sans Git Bash, un hook sous forme shell sans shell défini s'exécute dans PowerShell, où il peut échouer. Installez Git for Windows, ou définissez CLAUDE_CODE_GIT_BASH_PATH si Git Bash est installé à un endroit où Claude Code ne le trouve pas. Démarrez ensuite une nouvelle session.
  • Le hook exécute un seul .exe, pas un fichier .cmd ou .bat : placez le programme dans command et ses arguments dans args pour utiliser la forme exec, et ajoutez "onFailure": "block". Claude Code démarre alors le programme sans shell. Sans "onFailure": "block", l'appel peut se poursuivre lorsque le programme échoue, est absent ou expire
  • Un hook de garde PowerShell qui doit bloquer en cas d'échec : sans onFailure défini, il cesse de le faire une fois que Git for Windows est installé et que Claude Code trouve Git Bash. Ajoutez "onFailure": "block" afin qu'un échec ou un délai d'expiration bloque l'appel avec ou sans Git Bash. Définissez également "shell": "powershell" afin que le hook continue désormais de s'exécuter dans PowerShell plutôt que dans Git Bash

Avant la v2.1.309, le message était blocking because Git Bash wasn't found and this hook doesn't set "shell" to "powershell", so only exit code 0 or 2 counts as its verdict.

PowerShell started no program

Un hook avec cette erreur s'est exécuté dans PowerShell, qui imprime une command composée uniquement de chaînes entre guillemets, comme "command": "\"C:\\Tools\\guard.exe\"", au lieu de l'exécuter.

  • Pour imprimer du texte, comme du JSON : définissez "shell": "powershell", et le hook quitte avec 0
  • Pour exécuter un fichier .ps1, .cmd ou .bat, ou un .exe dont le chemin contient une variable : définissez "shell": "powershell" et placez & avant le chemin entre guillemets. Laissez $HOME tel quel, et écrivez les variables d'environnement comme l'exemple check.ps1 écrit $env:CLAUDE_PROJECT_DIR
  • Pour exécuter un seul .exe à un chemin sans $ : supprimez les guillemets internes de command, puis appliquez la correction pour un hook qui exécute un seul .exe, en écrivant "args": [] si le programme ne prend aucun argument
  • Pour exécuter un script écrit pour Bash : appliquez la correction pour un hook écrit pour Bash

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 sous-agent 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 s'est déjà 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 la requête, et aucune boîte de dialogue n'apparaît
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 fait échouer la suppression du worktree 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 sous-agent procède. Pour SubagentStart, l'avis apparaît dans la propre transcription du sous-agent, 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. Pour bloquer l'action lorsque la requête échoue ou retourne un statut non-2xx, définissez onFailure: "block". 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.

Après une erreur non-bloquante, l'action se poursuit sauf si le hook est l'un de ceux dont l'échec bloque l'action. Sauf sur les événements ayant leur propre contrat d'échec dans le tableau par événement, tels que WorktreeCreate, vous obtenez ces résultats pour chaque réponse ou échec :

  • 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
  • Défaillance de connexion : erreur non-bloquante
  • Délai d'expiration : le hook est annulé, comme décrit sous Délais d'expiration

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.

[!NOTE]

Choisissez une approche par hook : soit utiliser les codes de sortie seuls pour signaler, soit quitter 0 et imprimer JSON pour un contrôle structuré. Si vous les mélangez, exit 2 garde son effet de blocage, et Claude Code lit toujours les champs JSON, avec l'exception d'élicitation unique notée sous Exit code 2.

N'imprimez rien d'autre que l'objet JSON sur stdout. Pour un hook qui n'est pas async, tout autre texte à cet endroit, comme une ligne que votre profil shell affiche au démarrage, empêche Claude Code de lire l'objet comme JSON, et Hook JSON has no effect montre comment trouver et supprimer ce texte.

Les chaînes additionalContext, systemMessage et initialUserMessage d'un hook, ainsi que 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 : sauf pour le stdout de PreCompact, Claude Code enregistre la sortie dans un fichier du répertoire de session. Il remplace la sortie 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 plafond 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 plafond.

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. A priorité 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 consignée dans le journal de débogage
systemMessage aucun Message d'avertissement affiché à l'utilisateur. Dans la sortie Agent SDK et --output-format stream-json, il peut arriver comme un SDKInformationalMessage
terminalSequence aucun Une séquence d'échappement de terminal que Claude Code émet 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 d'autorisation, 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 en streaming.

É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 situation de concurrence 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 figurant dans la liste d'autorisation :

  • 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 d'autorisation, 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 lui-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 quelques limites :

  • Claude Code écrit la séquence uniquement dans une session interactive, et uniquement tant que son interface est ouverte. En mode non interactif avec le flag -p et dans l'Agent SDK, il ignore le champ.
  • Le champ fonctionne uniquement à partir d'un hook qui ne s'exécute pas en arrière-plan, donc laissez async et asyncRewake désactivés.
  • 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.

Si vous utilisez tmux ou GNU screen, effectuez d'abord la configuration correspondante. L'exemple ci-dessous déclenche une notification de bureau à partir d'un hook Notification dans un terminal qui lit OSC 777, comme Ghostty ou Warp. L'appel jq -nc --arg du script construit la sortie JSON et échappe les guillemets, barres obliques inverses et sauts de ligne présents dans le message de notification :

#!/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.

Recevoir des notifications de terminal dans tmux ou GNU screen

Lorsque Claude Code s'exécute dans tmux ou GNU screen, les séquences OSC ont besoin de ce qui suit pour parvenir à votre application de terminal :

  • tmux 3.3 ou ultérieur : ajoutez set -g allow-passthrough on à ~/.tmux.conf, puis exécutez tmux source-file ~/.tmux.conf depuis un shell. L'option s'applique à chaque programme de ces volets, pas seulement à Claude Code. Avec on, seuls les volets que tmux affiche peuvent les envoyer. Pour permettre également aux autres volets d'une session à laquelle vous êtes attaché de les envoyer, utilisez all à la place de on, sur tmux 3.4 ou ultérieur.
  • GNU screen : gardez la fenêtre de Claude Code affichée dans screen, car les séquences provenant d'une fenêtre en arrière-plan ne parviennent pas à votre application de terminal

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 requête au 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 votre chaîne contient une balise <system-reminder> ou </system-reminder>, Claude reçoit la chaîne avec le < de cette balise remplacé par &lt;.

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 lui 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 feature flags 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 en cours de 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 fait échouer la création
WorktreeRemove Exit code Tout code de sortie non-zéro fait échouer la suppression si le répertoire existe toujours après. La sortie JSON est rejetée
Elicitation, ElicitationResult hookSpecificOutput ou decision au niveau supérieur action (accept/decline/cancel), content (valeurs des champs de formulaire). decision: "block" refuse également
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 :

  • PreToolUse : updatedInput directement sous hookSpecificOutput remplace les arguments d'un outil avant son exécution. Consultez Contrôle de décision PreToolUse
  • PermissionRequest : updatedInput à l'intérieur de l'objet decision. Consultez Contrôle de décision PermissionRequest
  • PostToolUse : updatedToolOutput remplace le résultat de l'outil. Dans une session cloud Cowork, Claude Code ignore updatedToolOutput lorsque l'outil est AskUserQuestion ou ExitPlanMode, et Claude lit la réponse ou le plan d'origine. Consultez Contrôle de décision PostToolUse
  • UserPromptSubmit : ne peut pas remplacer le prompt ; injecte uniquement additionalContext à côté de celui-ci

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 :

[tab] Décision au niveau supérieur

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

[tab] PreToolUse

Utilise hookSpecificOutput pour un contrôle plus riche : autoriser, refuser, ou escalader à l'utilisateur. Vous pouvez également modifier l'entrée de l'outil avant son exécution ou injecter du contexte supplémentaire pour Claude. Consultez Contrôle de décision PreToolUse pour l'ensemble complet des options.

{
  "hookSpecificOutput": {
    "hookEventName": "PreToolUse",
    "permissionDecision": "deny",
    "permissionDecisionReason": "Database writes are not allowed"
  }
}

[tab] PermissionRequest

Utilise hookSpecificOutput pour autoriser ou refuser une demande de permission au nom de l'utilisateur. Lors de l'autorisation, vous pouvez également modifier l'entrée de l'outil ou appliquer des règles de permission afin que l'utilisateur ne soit pas sollicité à nouveau. Consultez Contrôle de décision PermissionRequest pour l'ensemble complet des options.

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

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 l'implémentation de référence du validateur de commandes Bash.

Événements de hook

Chaque événement correspond à un moment du cycle de vie de Claude Code où des hooks peuvent s'exécuter. Les sections ci-dessous suivent l'ordre de ce 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 : veillez donc à ce que ces hooks soient 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é lancé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 passez en arrière-plan

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 des 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 de ces attentes, appuyez sur Esc pour récupérer 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, éventuellement, 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 s'en servir 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 une version 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 prompt cache 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 prompt cache sur le modèle de la session, hors réponse

Cet exemple montre l'entrée d'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

Un hook SessionStart peut ajouter du contexte pour Claude, fournir le premier message utilisateur, définir le titre de la session, surveiller des fichiers et recharger les skills. Renvoyez le champ correspondant à chacune de ces actions, en plus des champs de sortie JSON disponibles pour tous les hooks :

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, en mode non interactif avec le flag -p. Elle devient le premier tour même si vous ne transmettez aucun prompt. Un prompt que vous transmettez suit comme tour suivant
sessionTitle Définit le titre de la session, avec le même effet que /rename. S'applique lorsque source vaut "startup", "resume" ou "fork"
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. Consultez Recharger les skills installés par un hook

Cette sortie ajoute du contexte et nomme la session :

{
  "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"
  }
}

Un hook qui se contente d'ajouter du contexte peut l'afficher sans construire de JSON, car Claude Code ajoute la sortie stdout en texte brut d'un hook SessionStart au contexte de Claude.

Si le hook SessionStart de votre plugin fournit initialUserMessage ou sessionTitle, installez le plugin avant le démarrage de la session. Claude Code ignore ces deux champs lorsqu'ils proviennent d'un plugin dont l'installation se termine après l'exécution des hooks SessionStart.

Recharger les skills installés par un hook

Pour rendre disponibles dans la même session les skills qu'un hook SessionStart installe, renvoyez reloadSkills. La découverte des skills s'exécute normalement avant la fin des hooks SessionStart ; sans ce champ, les fichiers qu'un hook écrit dans ~/.claude/skills/ ou .claude/skills/ peuvent donc être absents lors de l'exécution du premier prompt.

Cet exemple synchronise un dépôt de skills partagé et demande une 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 fictive. Remplacez-la par celle de votre propre dépôt de skills.

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 shell que Claude exécute plus tard dans la session.

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 de l'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

Chaque commande Bash exécute le contenu du fichier en tant que code shell avant la commande elle-même ; une ligne de ce fichier peut donc utiliser tout ce que Bash évalue, comme la référence $PATH dans export PATH="$PATH:./node_modules/.bin".

Variables conservées dans les commandes PowerShell

Les commandes PowerShell reçoivent également les variables de CLAUDE_ENV_FILE, dans Claude Code v2.1.296 ou une version ultérieure, mais PowerShell n'exécute jamais le fichier. Claude Code en extrait plutôt les affectations et les copie dans l'environnement de la commande PowerShell. Il ne le fait que lorsque chaque ligne correspond à l'un des cas suivants, pour l'ensemble de ce que chaque hook de cette session a écrit et de tout script sur lequel vous avez défini CLAUDE_ENV_FILE avant le lancement :

  • Une ligne vide ou un commentaire #
  • Une affectation unique en début de ligne, écrite export NAME=value, declare -x NAME=value ou NAME=value, avec une valeur que Bash utiliserait exactement telle qu'elle est écrite, composée de n'importe quelle combinaison des éléments suivants : du texte sans guillemets n'utilisant que des lettres, des chiffres et les caractères _ @ % + = : , . / -, du texte entre guillemets simples, et du texte entre guillemets doubles dans lequel chaque $, backtick ou " est échappé par une barre oblique inverse

Si une ligne est d'un autre type, comme export PATH="$PATH:./node_modules/.bin" avec son $PATH non échappé, une commande source ou les chaînes $'...' qu'affiche direnv export bash, les commandes PowerShell ne reçoivent aucune des variables, et claude --debug consigne Session environment is not all plain assignments. Les commandes Bash les reçoivent toujours toutes. Sous Windows, les commandes PowerShell ne reçoivent pas non plus une variable dont la valeur contient / ou \, car Git Bash et Windows écrivent les chemins différemment. Une commande PowerShell exécutée dans un sandbox ne reçoit aucune des variables.

[!NOTE]

CLAUDE_ENV_FILE est disponible pour les hooks SessionStart, Setup, CwdChanged et FileChanged. Les autres événements de hook n'ont pas accès à cette variable, pas plus qu'un hook qui s'exécute dans PowerShell, que ce soit via "shell": "powershell" ou par défaut sous Windows sans Git Bash.

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 la 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 ferme 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 vérifier que les hooks se sont exécutés, démarrez avec claude --debug-file <path> --init-only, en remplaçant <path> par l'emplacement d'un fichier de log, puis 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. La 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 ${CLAUDE_PLUGIN_DATA}/node_modules et exécute npm install en cas d'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, ce modèle n'est peut-être pas nécessaire : 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 se poursuit 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. Si votre hook écrit des variables dans ce fichier, elles sont conservées pour les commandes Bash suivantes de la session, comme pour 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 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 en revanche 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 le déclencher 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 qui a été 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" se déclenche lorsque des fichiers d'instructions sont rechargés après un événement de compaction
globs Motifs glob de chemins 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 sur les prompts que vous saisissez. Claude Code les exécute également lors de :

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

Hormis 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. Pour bloquer plutôt le prompt, définissez onFailure: "block" sur un hook de commande ou HTTP. La transcription affiche un avis indiquant le nom du hook, le délai d'expiration qui s'est déclenché et le fait que la sortie a été ignorée.

Un hook de 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 faire office de contrôle de politique qui ne doit pas laisser passer en cas d'échec. La session continue.

Esc pendant la vérification d'un prompt

Si vous appuyez sur Esc ou interrompez la session pendant que des hooks vérifient encore un prompt, la vérification s'arrête et le prompt ne parvient pas à Claude. Le texte que vous avez tapé dans le terminal revient dans le champ de saisie du prompt, et les hooks le vérifient à nouveau lorsque vous le renvoyez.

Un hook peut bloquer le prompt au moment même où vous interrompez. Le prompt reste alors bloqué : vous voyez le message de blocage de ce hook, et le texte ne revient pas dans le champ de saisie.

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

Lorsque le prompt est un skill ou une commande personnalisée que vous avez saisi, comme /deploy staging, le hook reçoit cette ligne telle que vous l'avez saisie, et non le contenu développé du skill. Si le hook bloque le prompt, la commande ne s'exécute pas, et les commandes shell que le skill injecte avec !`<command>` ne s'exécutent donc pas non plus. Pour effectuer la correspondance sur le nom ou les arguments de la commande, utilisez plutôt UserPromptExpansion.

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 :

  • 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é en tant que contexte

Aucun de ces 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 S'il vaut true lorsque le hook bloque le prompt, exclut le texte du prompt du message de blocage. Consultez Ce que laisse derrière lui un prompt bloqué

Un hook qui bloque en se terminant avec le code 2 suit le même acheminement que reason : le message de blocage affiche le texte 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 derrière lui 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.

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 un secret d'être é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 que 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 directement /skillname contourne PreToolUse. UserPromptExpansion se déclenche sur ce chemin direct.

Le matcher s'applique à 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 suit le même acheminement que reason : le message de blocage affiche le texte 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 s'affiche progressivement à l'écran. Claude Code affiche le message par incréments : chaque fois qu'un lot de lignes nouvellement terminées est prêt à être rendu, 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 génère plusieurs appels ; un message court peut n'en générer 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 des clés API ou des noms d'hôtes internes dans les réponses de Claude

Claude Code retient chaque lot jusqu'à ce que votre hook réponde : veillez donc à ce que le hook soit 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 ne concerne que 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 ; Claude ne voit donc jamais le remplacement, et le mode verbeux affiche l'original. Le hook ne reçoit que le texte des messages de l'assistant : les résultats d'outils et le texte que vous saisissez s'affichent donc 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 composées uniquement d'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 pour 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 messages de la transcription
index Index, en partant 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 finaux inclus. Toujours des lignes complètes, 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 ni 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.

[tab] macOS/Linux

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("`"; ""))}}'

[tab] Windows (PowerShell)

Enregistrez un hook de commande qui exécute le script via PowerShell :

{
  "hooks": {
    "MessageDisplay": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "powershell.exe",
            "args": [
              "-NoProfile",
              "-ExecutionPolicy",
              "Bypass",
              "-File",
              "${CLAUDE_PROJECT_DIR}/.claude/hooks/plain-display.ps1"
            ]
          }
        ]
      }
    ]
  }
}

Le flag -NoProfile évite de charger votre profil PowerShell afin que le hook démarre rapidement, et -ExecutionPolicy Bypass permet à PowerShell d'exécuter le fichier de script local.

Enregistrez ce script dans .claude/hooks/plain-display.ps1 dans votre projet :

$batch = [Console]::In.ReadToEnd() | ConvertFrom-Json
$text = $batch.delta -replace '\*\*', '' -replace '`', ''
@{
  hookSpecificOutput = @{
    hookEventName = "MessageDisplay"
    displayContent = $text
  }
} | ConvertTo-Json

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, et non 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 tous les noms d'outils 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 ceux-ci n'ont pas de contrôle de décision : ils ne peuvent donc pas bloquer l'écriture.

[!WARNING]

PreToolUse ne s'exécute que lorsque Claude appelle un outil. Par défaut, les fichiers que vous référencez avec @ dans votre prompt sont ajoutés sans aucun appel d'outil. Claude Code insère leur contenu lors de la construction du prompt : aucun hook PreToolUse ne se déclenche donc pour eux, y compris les hooks correspondant à Read. PreToolUse ne se déclenche pas non plus pour EndConversation.

Vous pouvez bloquer des chemins spécifiques dans les références @, ou désactiver l'insertion :

  • Bloquer des chemins spécifiques : ajoutez une règle de refus Read pour ces chemins
  • Désactiver l'insertion : définissez CLAUDE_CODE_DISABLE_FILE_MENTIONS=1 dans votre shell, vos paramètres utilisateur ou vos paramètres gérés. Claude reçoit alors les chemins mentionnés avec une note l'invitant à les lire à l'aide de ses outils de fichiers. S'il le fait, vos hooks PreToolUse correspondants s'exécutent. La variable nécessite Claude Code v2.1.299 ou une version ultérieure, et Claude Code l'ignore dans les paramètres de projet et locaux.

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

Un hook de callback de l'Agent SDK sur PreToolUse qui dépasse son délai d'expiration bloque l'appel d'outil.

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 répertorie 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 une version 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 ; un hook qui filtre sur les chemins ne peut donc 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 des 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 aucun 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 répertoriés. Nécessite Claude Code v2.1.269 ou une version ultérieure.

[!NOTE]

La liste est établie au mieux et est en bêta publique. Claude Code peut manquer une modification, inclure un fichier qu'un autre processus a modifié au même moment, ou s'arrêter à ses limites de taille. La structure du champ peut changer. Utilisez la liste pour savoir quoi examiner, pas pour appliquer une politique.

changedFiles et files répertorient 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, destinés à 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, telle que git checkout ou git stash, de sorte que Claude Code ne calcule aucun diff
shared boolean true Défini lorsqu'un autre appel de l'outil Bash, comme celui d'un sous-agent, s'est exécuté dans le même dépôt au même moment ; certaines modifications répertoriées peuvent donc provenir de cette commande
PowerShell

Exécute des commandes PowerShell. Consultez l'outil PowerShell pour connaître sa disponibilité selon la plateforme.

Les champs sont les mêmes que pour 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 achemine 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

Recherche 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 à l'aide d'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é
offset number 100000 Nombre facultatif de caractères à ignorer depuis le début de la page. Claude le définit pour poursuivre la lecture d'une longue page. Nécessite Claude Code v2.1.290 ou une version ultérieure

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
mode string "extended" Facultatif : type de recherche. "standard" demande une recherche plus légère et "extended" la recherche complète. Certaines sessions n'envoient pas mode, et un appel sans ce champ exécute la recherche complète. Nécessite Claude Code v2.1.282 ou une version ultérieure
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 examiner l'exécution ; pour des totaux 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. Pour savoir quels sous-agents s'exécutent de chaque manière, consultez Exécuter les sous-agents au premier plan ou en arrière-plan
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 transmission à 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
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 par type des tokens de la requête API finale : input_tokens, output_tokens, cache_creation_input_tokens, cache_read_input_tokens

Dans Claude Code v2.1.271 ou une version 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 transmission 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 ; tool_response ne contient donc 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. Il contient status: "async_launched", agentId, description, prompt, outputFile et resolvedModel.

Sur 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. Sur une réponse async_launched, resolvedModel indique le modèle utilisé au moment où l'agent est passé en arrière-plan ; un changement survenu avant le passage en arrière-plan y est donc reflété.

AskUserQuestion

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

Champ Type Exemple Description
questions array [{"question": "Which framework?", "header": "Framework", "options": [{"label": "React", "description": "Component library"}, {"label": "Vue", "description": "Progressive framework"}], "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 chaque 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 ; le tool_input littéral provenant du modèle est donc 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 comportant les champs plan et filePath qui contiennent 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 offre un contrôle plus riche : quatre issues (allow, deny, ask ou defer) ainsi que la possibilité de modifier l'entrée de l'outil avant l'exécution. Pour autoriser ou réécrire une commande Monitor, consultez Filtrer les commandes Monitor.

Champ Description
permissionDecision "allow" ignore la demande de permission, sauf pour les actions qu'aucun mode n'approuve automatiquement et les lectures depuis des chemins réseau. Il n'ignore pas non plus l'approbation des appels aux outils qu'une application Agent SDK marque always_ask, sauf si le mode de permission approuve l'appel. AskUserQuestion et ExitPlanMode nécessitent updatedInput associé à "allow". Pour le mode plan, consultez Approbations par hook en mode plan. "deny" empêche l'appel d'outil. "ask" demande à l'utilisateur de confirmer. "defer" termine le tour afin que l'outil puisse être repris plus tard, et nécessite le mode non interactif avec le flag -p. Les règles de refus et de demande sont toujours évaluées, quelle que soit la valeur renvoyée par le hook
permissionDecisionReason Pour "ask", affiché à l'utilisateur dans la demande de permission. Lorsque Claude Code refuse l'appel lors d'une exécution -p où personne ne peut répondre à cette demande, Claude lit plutôt la raison dans le résultat de l'outil. 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'objet d'entrée entier ; incluez donc les champs inchangés avec les 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 à celle envoyée par Claude. Combinez-le avec "allow" pour approuver automatiquement, ou avec "ask" pour montrer l'entrée modifiée à l'utilisateur. Ignoré pour "defer". Sans permissionDecision, le mode auto peut refuser l'appel modifié
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 suit le même acheminement que "deny" : Claude voit le message stderr comme raison du refus.

Lorsqu'un hook renvoie "ask", la demande de permission affichée à l'utilisateur comprend une étiquette indiquant 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 la confirmation.

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

[!NOTE]

PreToolUse utilisait auparavant les champs decision et reason de premier niveau, mais ceux-ci sont dépréciés pour cet événement. Utilisez plutôt hookSpecificOutput.permissionDecision et hookSpecificOutput.permissionDecisionReason. Les valeurs dépréciées "approve" et "block" correspondent respectivement à "allow" et "deny". D'autres événements comme PostToolUse et Stop continuent d'utiliser decision et reason de premier niveau comme format actuel.

updatedInput de PreToolUse en mode auto

Lorsque votre hook renvoie updatedInput sans permissionDecision en mode auto, Claude Code peut refuser l'appel modifié. Le serveur a examiné l'entrée écrite par Claude, et non celle renvoyée par votre hook. Dans les sessions de terminal interactives et les sessions VS Code connectées directement à l'API Anthropic, le refus inclut ce texte :

a hook changed this call's input after auto mode reviewed it

Après un refus comportant ce texte, Claude peut émettre à nouveau l'appel modifié afin que le serveur l'examine. Si Claude répète exactement l'entrée modifiée dans sa réponse suivante, votre hook s'exécute toujours :

  • Il peut refuser l'appel ou l'envoyer vers la demande de permission
  • Tout updatedInput qu'il renvoie est ignoré ; ce qui s'exécute est donc ce que le serveur a examiné

Vous n'avez pas besoin de modifier votre hook en raison de ce refus, sauf si Claude vous indique dans sa réponse que le mode auto n'a pas pu vérifier l'appel.

Dans les sessions qui affichent ce texte, un hook command défini dans les paramètres ou dans le fichier de hooks d'un plugin peut modifier les appels Bash, PowerShell, Monitor, Edit, Write et NotebookEdit sans provoquer ce refus. Le refus peut tout de même apparaître, par exemple lorsqu'un hook a modifié un appel antérieur dans la même réponse de Claude ou lorsqu'une commande Bash demande à s'exécuter en dehors du sandbox.

Décisions ask de PreToolUse en mode auto

En mode auto, le "ask" d'un hook PreToolUse envoie normalement l'appel d'outil vers la demande de permission, et le classifieur ne statue pas dessus. Certains appels suivent un chemin différent :

  • Appels que les propres vérifications de Claude Code signalent également : il s'agit notamment des écritures dans des chemins protégés et des commandes qui demandent à s'exécuter hors du sandbox. Le classifieur peut refuser l'un de ces appels avant qu'une demande n'apparaisse. S'il l'approuve, l'appel passe normalement quand même par la demande.
  • Appels SubagentHandback : cet outil transmet le rapport d'un sous-agent. Aucune demande n'apparaît pour lui, et l'appel s'exécute même si le classifieur s'y oppose.

Pour arrêter un appel SubagentHandback, renvoyez "deny". Claude Code indique alors au sous-agent, jusqu'à trois fois, que son rapport n'a pas été transmis et qu'il doit rappeler l'outil. Si votre hook continue de refuser, le sous-agent se termine sans transmettre de rapport via cet outil, et la conversation qui l'attend reçoit une note l'indiquant.

Outils nécessitant une interaction utilisateur

AskUserQuestion et ExitPlanMode nécessitent une interaction utilisateur. En mode non interactif avec le flag -p, Claude Code ne les propose que lorsque l'exécution dispose d'un hôte de permissions pour recevoir la demande, comme un callback canUseTool de l'Agent SDK.

Un hook PreToolUse satisfait cette exigence lorsqu'il effectue les opérations suivantes :

  1. Il lit l'entrée de l'outil depuis stdin
  2. Il recueille la réponse via votre propre interface utilisateur
  3. Il renvoie permissionDecision: "allow" accompagné d'un updatedInput contenant la réponse, 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. Cette sortie répond à une question avec React :

{
  "hookSpecificOutput": {
    "hookEventName": "PreToolUse",
    "permissionDecision": "allow",
    "updatedInput": {
      "questions": [
        {
          "question": "Which framework?",
          "header": "Framework",
          "options": [{"label": "React", "description": "Component library"}, {"label": "Vue", "description": "Progressive framework"}],
          "multiSelect": false
        }
      ],
      "answers": {"Which framework?": "React"}
    }
  }
}

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 vérifier 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 en tant que sous-processus et lisent sa sortie JSON, comme une application Agent SDK ou une interface utilisateur personnalisée construite sur Claude Code. Il permet à ce processus appelant de mettre Claude en pause lors d'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 "defer" qu'en mode non interactif avec le flag -p, et uniquement lorsque Claude effectue un seul appel d'outil dans le tour :

  • Dans les sessions interactives : il consigne un avertissement et ignore le résultat du hook
  • Plusieurs appels d'outils à la fois : "defer" est ignoré avec un avertissement et l'outil suit le flux de permission normal

L'outil AskUserQuestion en est le cas typique : Claude veut poser une question à l'utilisateur, mais aucun terminal ne permet d'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 tour se termine avec un résultat portant stop_reason: "tool_deferred", et l'appel d'outil en attente reste dans la transcription.
  3. Le processus appelant lit deferred_tool_use dans le résultat du SDK, affiche la question dans sa propre interface utilisateur 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 :

{
  "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", "description": "Component library"}, {"label": "Vue", "description": "Progressive framework"}], "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 le nettoyage de rétention cleanupPeriodDays la supprime, après 30 jours par défaut.

Si la réponse n'est pas prête lorsque vous reprenez, le hook peut renvoyer à nouveau "defer". Si vous avez envoyé un prompt avec cette reprise, Claude y répond sans le résultat de l'outil et n'exécute pas l'appel. Reprenez sans prompt jusqu'à ce que le hook renvoie "allow" ou "deny".

[!NOTE]

Pour reprendre une session différée en mode plan, transmettez --permission-prompt-tool avec --resume afin que Claude Code puisse présenter le plan pour approbation. Pour les autres conditions, consultez Reprendre en mode plan avec -p. Nécessite Claude Code v2.1.246 ou une version ultérieure.

Lorsque vous reprenez avec -p, Claude Code ne restaure aucun autre mode de permission enregistré. Il démarre l'exécution dans le mode de permission dans lequel démarrerait une nouvelle exécution claude -p ; transmettez donc à nouveau --permission-mode ou --dangerously-skip-permissions si la session différée en utilisait un.

Reprendre lorsqu'un outil différé est indisponible

Un résultat avec stop_reason: "tool_deferred_unavailable" signifie que l'outil d'un appel différé n'est pas disponible dans la session reprise, par exemple parce que son serveur MCP n'est pas connecté. Votre hook PreToolUse ne se déclenche pas, et le champ deferred_tool_use du résultat nomme l'outil.

Tant que l'appel est en attente, aucun prompt que vous envoyez n'atteint Claude et l'exécution se termine avec le code 1. Le champ result commence par <tool> isn't available, so its deferred call didn't run. Exécutez l'appel, ou abandonnez-le pour qu'il ne s'exécute pas :

  • Pour exécuter l'appel : rendez l'outil disponible et reprenez à nouveau.
  • Pour abandonner l'appel : reprenez avec un prompt et CLAUDE_CODE_DROP_UNAVAILABLE_DEFERRED_TOOL défini sur 1. Claude reçoit une erreur comme résultat de l'appel et répond à votre prompt. Lorsque vous reprendrez plus tard, aucun appel ne sera en attente.

Lorsque vous abandonnez l'appel, l'exécution écrit le résultat tool_deferred_unavailable avec un champ result vide, puis la réponse de Claude dans un second résultat. Elle se termine avec le code 0, sauf si cette réponse est une erreur. Si vous définissez la variable sans envoyer de prompt, l'exécution abandonne tout de même l'appel et se termine avec le code 1.

Avant la v2.1.319, Claude Code répondait à un prompt sans le résultat de l'outil et abandonnait l'appel. Sur ces versions, envoyez un nouveau prompt une fois l'outil disponible.

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 c'est la première décision prise qui 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 même où Claude demande la permission d'utiliser un outil. Claude Code n'exécute un hook Notification avec le 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 un sandbox. Pour obtenir un signal pour cette demande, utilisez le type de notification permission_prompt.

Le matcher s'applique au nom de l'outil, avec les mêmes valeurs que pour PreToolUse.

Entrée 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 permission_suggestions facultatif 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éduisent 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 aussi proposer des options sans entrée de suggestion correspondante, 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 ou non une permission. Les hooks PermissionRequest ne s'exécutent que lorsque Claude Code s'apprête à vous demander une permission, ou lorsqu'il refuserait sinon automatiquement un appel qui ne peut pas afficher de demande. Aucun de ces é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 PermissionRequest

Les hooks PermissionRequest peuvent autoriser ou refuser les 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'objet d'entrée entier, alors incluez les champs inchangés avec ceux modifiés. L'entrée modifiée est réévaluée par rapport aux règles de refus et de demande. AskUserQuestion et ExitPlanMode nécessitent ce champ
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

Pour autoriser ou réécrire une commande Monitor, consultez Faire correspondre les commandes Monitor.

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. Pour accorder ou refuser la demande, renvoyez l'objet decision.

{
  "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 l'endroit 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 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
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

Dans rules, pour une règle de permission écrite Tool(specifier), toolName est le nom de l'outil et ruleContent est le spécificateur :

  • Bash(npm test) : { "toolName": "Bash", "ruleContent": "npm test" }
  • Bash ou Bash(*) : { "toolName": "Bash" }, car les deux ciblent l'outil entier
  • mcp__github__get_issue : { "toolName": "mcp__github__get_issue" }

N'incluez pas ruleContent dans une règle d'autorisation pour un outil MCP. Si vous l'ajoutez, la règle ne correspond à aucun appel.

[!NOTE]

setMode avec bypassPermissions ne prend effet que si vous avez lancé la session avec le mode bypass déjà disponible : --dangerously-skip-permissions, --permission-mode bypassPermissions, --allow-dangerously-skip-permissions, ou permissions.defaultMode: "bypassPermissions" dans les paramètres utilisateur, --settings ou les paramètres gérés. Sinon, la mise à jour n'a aucun effet. La mise à jour n'a également aucun effet lorsque permissions.disableBypassPermissionsMode désactive le mode, ou lorsque la session démarre en mode restreint.

bypassPermissions n'est jamais conservé comme defaultMode, quelle que soit la destination.

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 la réussite d'un outil.

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

Utilisez une correspondance plus large 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 également 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.

PostToolUse après une interruption

Si un hook PostToolUse s'arrête prématurément après que vous avez envoyé un message ou interrompu pendant un appel d'outil, vérifiez si vous avez appuyé sur Ctrl+Enter, Enter ou Esc :

  • Ctrl+Enter : si appuyer sur cette touche termine le tour pendant qu'un outil s'exécute et que l'outil se termine, vos hooks PostToolUse s'exécutent sur son résultat et disposent de 10 secondes pour se terminer. La touche agit comme Esc si vous avez mis en file d'attente une commande ! avant vos messages ou si les tâches en arrière-plan sont désactivées, par exemple avec CLAUDE_CODE_DISABLE_BACKGROUND_TASKS.
  • Enter : un message que vous mettez en file d'attente avec Enter n'interrompt pas le tour, de sorte que les hooks conservent leur timeout habituel.
  • Esc : vous annulez immédiatement les hooks ; envoyez donc le message avec Enter ou Ctrl+Enter lorsque les hooks doivent s'exécuter.

Une fois les 10 secondes écoulées, Claude Code arrête un hook de commande qui s'exécute encore, quel que soit son timeout. Pour laisser un hook de commande plus long se terminer, exécutez-le avec async: true, ce qui signifie que le hook ne peut ni bloquer ni réécrire le résultat.

Avant la v2.1.319, les hooks ne démarraient pas après que vous aviez interrompu le tour avec un message, et un hook en cours d'exécution était arrêté.

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 des deux 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 montrée à Claude lorsque decision vaut "block"
additionalContext Chaîne ajoutée au contexte de Claude avec le résultat de l'outil. Consultez 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. Consultez Annoter un résultat pour le classifieur du mode auto. Nécessite Claude Code v2.1.236 ou une version ultérieure
updatedToolOutput Remplace la sortie de l'outil par la valeur fournie avant son envoi à Claude. La valeur doit correspondre à la forme de sortie de l'outil. Dans une session cloud Cowork, Claude Code ignore ce champ lorsque l'outil est AskUserQuestion ou ExitPlanMode, et Claude lit la réponse ou le plan d'origine
updatedMCPToolOutput Remplace la sortie pour les outils MCP uniquement. Préférez updatedToolOutput, qui fonctionne aussi pour les outils intégrés

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
    }
  }
}

[!WARNING]

updatedToolOutput ne modifie que ce que Claude voit. L'outil s'est déjà exécuté au moment où le hook se déclenche, donc tous les fichiers écrits, les commandes exécutées ou les requêtes réseau envoyées ont déjà pris effet. La télémétrie, comme les spans d'outils OpenTelemetry et les événements d'analyse, capture également la sortie d'origine avant l'exécution du hook. Pour empêcher ou modifier un appel d'outil avant son exécution, utilisez plutôt un hook PreToolUse.

La valeur de remplacement doit correspondre à la forme de sortie de l'outil. Les outils intégrés renvoient des objets structurés plutôt que de simples chaînes. Par exemple, Bash renvoie un objet avec les champs stdout, stderr, interrupted et isImage. Pour les outils intégrés, une valeur qui ne correspond pas au schéma de sortie de l'outil est ignorée et la sortie d'origine est utilisée. La sortie des outils MCP est transmise sans validation de schéma. Supprimer des détails d'erreur dont Claude a besoin peut l'amener à poursuivre sur une hypothèse erronée.

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 pris en charge 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 une version ultérieure.

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 de fichiers de paramètres, de plugins, de skills et du frontmatter d'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 en tant que callback du SDK TypeScript et renvoie la note pendant la session active, 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. Le 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 ignore 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

[!WARNING]

Le classifieur lit le contenu que vous placez dans classifierContext comme une information provenant de l'application qui héberge la session, ne copiez donc pas de sortie d'outil non fiable ou de texte tiers dans ce champ. Limitez la note à une courte affirmation sur cet unique appel, comme un fait sur son origine ou une déclaration de l'utilisateur à son sujet ; n'utilisez pas ce champ pour transmettre des messages sans rapport ou un flux d'événements.

PostToolUseFailure

S'exécute lorsqu'un outil qui a commencé à s'exécuter é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.

[!NOTE]

Cet événement ne se déclenche pas pour les appels d'outils rejetés avant l'exécution : un nom d'outil inconnu, une entrée qui échoue à la validation du schéma ou à la validation propre à l'outil, ou un refus de permission. Les rejets de validation sont renvoyés sous forme de résultats tool_use_error et se produisent avant l'exécution des hooks, ils ne déclenchent donc ni PreToolUse ni PostToolUseFailure. Les refus de permission déclenchent PreToolUse mais pas cet événement ; consultez PermissionDenied.

Entrée PostToolUseFailure

Les hooks PostToolUseFailure reçoivent les mêmes champs tool_name et tool_input que PostToolUse, ainsi que des informations d'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 a atteint 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 plutôt 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 l'é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 et terminée produit une première ligne Exit code N, puis toute sortie produite par la commande sous la forme d'un seul bloc où stdout et stderr sont entrelacé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 les longues chaînes en leur milieu 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. Consultez 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 en parallèle. PostToolBatch se déclenche exactement une fois avec le lot complet, c'est donc l'endroit idéal pour injecter du contexte qui dépend de l'ensemble des outils exécutés plutôt que d'un outil en particulier. 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, analysez donc uniquement les champs dont vous avez besoin.

[!NOTE]

La forme de tool_response diffère de celle de PostToolUse. PostToolUse transmet l'objet Output structuré de l'outil, comme {filePath: "...", type: "create"} pour Write ; PostToolBatch transmet le contenu tool_result sérialisé que voit le modèle.

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 l'appel suivant au modèle. Consultez 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 l'appel suivant 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, donc Claude le voit lorsque la conversation se poursuit.

PermissionDenied

S'exécute lorsque le mode auto refuse un appel d'outil, y compris lorsqu'il refuse sans verdict du classifieur parce qu'une vérification de sécurité distincte du mode auto a refusé la propre requête du classifieur 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 La raison du refus. Pour un verdict du classifieur, dans la plupart des sessions, elle nomme la règle correspondante entre crochets, comme [Data Exfiltration] ; consultez Examiner les refus pour les autres formes. Pour un refus sans verdict, elle 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 à la conversation un message 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 une vérification de sécurité distincte du mode auto a refusé la propre requête du classifieur. 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 façon dont vous êtes alerté, et non l'exécution de votre hook.

Matcher Moment du déclenchement
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 est terminée
auth_storage_failure Votre connexion se termine, mais Claude Code ne peut pas enregistrer les nouveaux identifiants dans son stockage d'identifiants. Se déclenche à la place de auth_success
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 montre la question de configuration du terminal d'un coéquipier d'une équipe d'agents ou l'avis du mode auto concernant les frais des 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 : lors de 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 à nouveau l'utilisation disponible, avec l'exception relative au paramètre de modèle
quota_auto_resume_stale Une limite d'utilisation de claude.ai a été réinitialisée alors que votre ordinateur était en veille depuis plus d'environ 30 minutes. Claude Code attend que vous appuyiez sur Enter au lieu de continuer. Après une veille plus courte, il continue et déclenche plutôt quota_auto_resume_fired
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 à 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 choisissez Don't continue automatically
plugin_notification Un mod émet une notification avec $.ui.notify. Le title du hook est le nom du mod, sauf si le mod en a défini un

Les types quota_auto_resume_fired, quota_auto_resume_stale et quota_auto_resume_disabled nécessitent Claude Code v2.1.234 ou une version ultérieure. Le type auth_storage_failure nécessite v2.1.288 ou une version ultérieure.

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 une version ultérieure.

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

[!NOTE]

Les types permission_prompt, idle_prompt, elicitation_dialog et elicitation_url_dialog partagent leur temporisation avec les notifications de bureau, donc dans les sessions de terminal, vous ne les voyez que lorsque vous semblez être éloigné du terminal :

  • Attendez-vous à permission_prompt une fois que vous n'avez rien saisi depuis environ six secondes. Le minuteur démarre lorsque la demande de permission apparaît, et chaque frappe le repousse. Pour exécuter un hook immédiatement lorsque Claude demande la permission d'utiliser un outil, utilisez plutôt PermissionRequest.
  • Attendez-vous à idle_prompt environ 60 secondes après que Claude a fini de répondre, et seulement si vous n'avez rien saisi depuis et qu'aucun agent en arrière-plan, comme un sous-agent en arrière-plan, n'est encore en cours d'exécution. Claude Code n'envoie pas idle_prompt pendant qu'il attend la réinitialisation d'une limite d'utilisation de claude.ai. Lorsque l'attente se termine d'elle-même, l'un des types quota_auto_resume_* se déclenche à la place.
  • Attendez-vous à elicitation_dialog pour un formulaire d'élicitation, ou à elicitation_url_dialog pour une demande d'URL dans le navigateur, une fois que vous n'avez rien saisi depuis environ six secondes. Tous deux partagent le même seuil de six secondes que permission_prompt : le minuteur démarre lorsque la boîte de dialogue apparaît, et chaque frappe le repousse.

Une demande de permission ou une élicitation qui arrive alors qu'une autre boîte de dialogue est affichée conserve le même seuil de six secondes, calculé à partir de l'arrivée de la demande. Sa notification peut vous parvenir alors que la demande attend encore derrière la boîte de dialogue ouverte.

Claude Code temporise permission_prompt différemment dans les sessions où il envoie les demandes de permission au callback canUseTool de l'Agent SDK, c'est-à-dire 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 repousse pas pendant que vous saisissez.
  • 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 des gestionnaires différents 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 ignore leurs champs systemMessage et continue mais émet tout de même terminalSequence, sur lequel repose l'exemple de notification de bureau. Les hooks Notification sont destinés à des 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 à portée de plugin, comme my-plugin:reviewer, et non le simple nom du frontmatter. Les deux-points font passer un nom à portée de plugin par le chemin 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 du 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. Consultez 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 laisse intact le cache de prompts 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. 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 prompts 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, comme celui 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 qui correspond à une chaîne vide, s'exécute également pour les événements avec un agent_type vide.

Avec Claude Code v2.1.271 ou une version ultérieure, 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 correspond à 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. Les deux tableaux sont limités à la session parente, et non au 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 hors 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, facultativement, 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
agent_id Pour cet événement, le champ d'entrée commun identifie le sous-agent ou le coéquipier en processus qui crée la tâche. Peut être absent. Nécessite Claude Code v2.1.290 ou une version ultérieure

Contrôle de décision TaskCreated

Un hook TaskCreated peut bloquer la création avec le code de sortie 2 ou avec une décision JSON. 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 stderr comme message.
  • JSON {"decision": "block", "reason": "..."} : Claude Code renvoie reason comme message.

Cet exemple bloque les tâches dont les sujets ne respectent 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 train d'être marquée comme terminée. Cela se produit 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 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, facultativement, 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
agent_id Pour cet événement, le champ d'entrée commun identifie le sous-agent ou le coéquipier en processus qui termine la tâche. Peut être absent. Nécessite Claude Code v2.1.290 ou une version ultérieure

Contrôle de décision TaskCompleted

Les hooks TaskCompleted prennent en charge deux façons 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": "..."} : lorsqu'un coéquipier terminant son tour a déclenché l'événement, arrête complètement le coéquipier, conformément au comportement du hook Stop. Le stopReason est affiché à l'utilisateur. Lorsque l'outil TaskUpdate a déclenché l'événement, 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 plutôt StopFailure.

[!TIP]

La commande /goal est un raccourci intégré pour un hook Stop basé sur un prompt et limité à la session. Utilisez-la lorsque vous souhaitez que Claude continue de travailler vers une condition sans écrire de configuration de hook.

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 continue 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 des hooks Stop ont fait continuer le tour huit fois de suite, Claude Code passe outre le blocage suivant et termine le tour. Le compteur de poursuites consécutives est réinitialisé chaque fois que Claude appelle un outil. Pour relever le 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 inclue 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 ou 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. Se rabat sur le discriminant brut pour les types non reconnus
status Statut actuel de la tâche
description Description en texte libre, plafonnée à 1 000 caractères avec un marqueur … [+N chars] dans la chaîne lorsqu'elle est tronquée
command Ligne de commande shell, plafonnée à 1 000 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 un unique moment de déclenchement, true pour les tâches qui se redéclenchent à chaque correspondance
prompt Prompt soumis lorsque le cron se déclenche, plafonné à 1 000 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 hors erreur pour Claude. La conversation continue afin que Claude puisse y donner suite, mais contrairement à decision: "block", il apparaît 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 maintient 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 le libelle 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 entreprendre des actions 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, error_details facultatif et 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 de qualité avant qu'un coéquipier n'arrête de travailler, comme exiger la réussite des vérifications lint ou vérifier que les fichiers de sortie existent.

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
agent_id Pour cet événement, le champ d'entrée commun identifie le coéquipier en processus qui est sur le point de devenir inactif. Peut être absent. Nécessite Claude Code v2.1.290 ou une version ultérieure

Contrôle de décision TeammateIdle

Les hooks TeammateIdle prennent en charge deux façons de contrôler le comportement des coéquipiers :

  • 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, conformément au 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, lors de son interrogation de la politique, un fichier de paramètres gérés côté Windows modifié sans les exécuter.

Le matcher filtre sur la source de configuration :

Matcher Moment du déclenchement
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 pour un 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, facultativement, file_path. Le champ source indique quel type de configuration a changé, et file_path fournit le chemin du fichier spécifique 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 les modifications de configuration de prendre effet. Utilisez le code de sortie 2 ou une decision JSON pour empêcher la modification. Lorsqu'elle est bloquée, 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, vous pouvez donc 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 provenant de la sortie JSON d'un hook ConfigChange et ignore 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 les variables d'environnement, activer des chaînes d'outils propres au projet ou exécuter automatiquement des scripts de configuration. S'associe à FileChanged pour des outils comme direnv qui gèrent l'environnement par répertoire.

Les hooks CwdChanged ont accès à CLAUDE_ENV_FILE. Si votre hook écrit des variables dans ce fichier, elles persistent dans les commandes Bash suivantes jusqu'au prochain événement CwdChanged, 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 que FileChanged surveille :

Champ Description
watchPaths Tableau de chemins absolus. Remplace la liste de surveillance dynamique actuelle. Les chemins issus de la configuration de votre 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 depuis leur sortie JSON et ignore 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 transmettez 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 de hook 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 façon dont le répertoire a été ajouté :

Matcher Moment du déclenchement
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 façon 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 ignore le champ continue de leur sortie JSON et traite le reste différemment selon la source :

  • slash_command : Claude Code transmet le systemMessage du hook à Claude comme contexte au tour de conversation suivant, plutôt que de vous l'afficher. Un décompte des hooks en échec apparaît dans la transcription. La sortie d'échec complète est envoyée dans le log de débogage
  • register_repo_root : Claude Code écrit la sortie systemMessage et la sortie d'échec 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 à l'aide d'un observateur du système de fichiers, et non en inspectant les appels d'outils, de sorte qu'il exécute le hook quelle que soit l'origine de la modification du fichier : 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 les variables d'environnement lorsque les fichiers de configuration du projet changent.

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

  • Construire la liste de surveillance : la valeur est découpée selon | et chaque segment est enregistré comme un nom de fichier littéral dans le répertoire de travail, de sorte que ".envrc|.env" surveille exactement ces deux fichiers. Les motifs regex 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 en appliquant les règles de matcher standard au nom de base du fichier modifié.

Cet exemple normalise les fins de ligne dans 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é depuis le champ file_path de l'entrée JSON sur stdin. Sa garde grep teste exactement 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 moins stricte boucle indéfiniment, car perl -i réécrit le fichier même lorsqu'il ne substitue rien, et Claude Code exécute à nouveau 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 la liste de surveillance dynamiquement. Claude Code ne démarre l'observateur que lorsque quelque chose nomme un fichier à surveiller ; initialisez 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 ; omettez donc le matcher du groupe qui gère les chemins dynamiques : il correspond alors à 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éralement 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, lorsque 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 modifié
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 ignore 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 lorsque Claude Code crée un worktree, par exemple dans les cas suivants :

Un hook WorktreeCreate ne s'exécute pas pour un worktree que Claude ajoute via Bash, et le hook d'un plugin ne s'exécute pas pour celui créé lorsque vous lancez avec --worktree et --tmux :

  • git worktree add via Bash : pour inspecter ou bloquer cette commande, ajoutez un hook PreToolUse avec le matcher Bash.
  • --worktree avec --tmux : Claude Code crée le worktree avant de charger les plugins, de sorte qu'un hook WorktreeCreate provenant d'un plugin ne s'exécute pas. Pour que votre hook s'exécute avec les deux flags, définissez-le plutôt dans un fichier de paramètres. Avant la v2.1.300, le hook WorktreeCreate d'un plugin ne s'exécutait pas non plus avec --worktree seul.

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. Consultez Sortie WorktreeCreate pour savoir comment chaque type de hook renvoie le chemin.

Claude Code agit en fonction de la réussite du hook et du chemin renvoyé, et ignore 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 depuis l'entrée JSON sur stdin, extrait une nouvelle copie dans un nouveau répertoire et affiche le chemin du répertoire. Le 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, spécifié par l'utilisateur ou 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 WorktreeCreate

Les hooks WorktreeCreate n'utilisent pas le modèle de décision standard autoriser/bloquer. C'est la réussite ou l'échec du hook qui détermine le résultat. Le hook doit renvoyer le chemin du 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 tous les 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 qui passe par un lien symbolique situé sous la racine du dépôt. L'erreur indique le composant rejeté. Un lien symbolique commité dans le dépôt pourrait rediriger le worktree hors de celui-ci. Renvoyez un chemin normalisé qui ne passe par aucun lien symbolique à l'intérieur du dépôt.

WorktreeRemove

S'exécute lorsque Claude Code nettoie un worktree créé par votre hook WorktreeCreate. L'événement se déclenche lorsque :

  • Vous quittez une session de worktree interactive et choisissez de supprimer le worktree lorsque Claude Code vous le demande
  • Vous quittez une session de worktree interactive que vous n'avez pas nommée, Claude Code ne trouve aucun fichier modifié ou non suivi, et il supprime le worktree sans vous le demander
  • Vous supprimez une session en arrière-plan qui s'exécute dans le worktree
  • Vous demandez à Claude de quitter et de supprimer le worktree pendant une session, et Claude le supprime avec l'outil ExitWorktree
  • Une session que claude remote-control a démarrée en mode worktree se termine sans plantage, et Claude Code ne trouve aucun fichier modifié ou non suivi ni aucun nouveau commit dans son worktree
  • Vous arrêtez claude remote-control alors que des sessions qu'il a démarrées en mode worktree sont encore en cours d'exécution, et Claude Code ne trouve aucun fichier modifié ou non suivi ni aucun nouveau commit dans leurs worktrees

Lorsqu'un sous-agent ou un agent de workflow termine, Claude Code conserve le worktree que votre hook WorktreeCreate a créé pour lui et n'exécute pas votre hook WorktreeRemove. Supprimez vous-même ces worktrees une fois le travail terminé.

Claude Code ne vous demande rien avant que votre hook s'exécute pour un appel ExitWorktree ou un nettoyage claude remote-control. ExitWorktree refuse de supprimer un worktree créé par un hook à moins que l'appel ne transmette discard_changes: true, ce qui fait de votre hook la dernière vérification sur ce chemin.

Claude Code utilise git pour rechercher les fichiers modifiés ou non suivis ; il n'en trouve donc aucun dans un worktree qui n'est pas un checkout git ou qui ne se trouve pas dans un checkout git, même lorsque le répertoire contient du travail non commité. Vérifiez la présence de ce travail dans votre hook WorktreeRemove avant qu'il ne supprime quoi que ce soit.

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 :

  • Aucun hook WorktreeRemove : Claude Code se rabat sur git pour une suppression à la sortie ou via ExitWorktree, et conserve le worktree lors du nettoyage par claude remote-control :
    • Vous quittez une session de worktree, ou Claude appelle ExitWorktree : Claude Code supprime le chemin renvoyé par votre hook WorktreeCreate comme le ferait git worktree remove --force, 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.
    • claude remote-control nettoie le worktree d'une session : le worktree reste sur le disque, et le terminal qui exécute claude remote-control affiche worktree removal failed, kept: <path>.
    • Vous supprimez une session en arrière-plan : consultez Ce que supprime la suppression d'une session.
  • 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 encore 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 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.

Pour la suppression d'une session en arrière-plan, Claude Code vérifie le chemin du worktree enregistré avant d'exécuter le hook, en refusant un chemin qui est un lien symbolique ou qui passe par un lien symbolique sous la racine du dépôt. Dans ce cas, le hook ne s'exécute pour un worktree contenant encore des fichiers que lorsque vous confirmez la suppression dans la vue agent. Pour un tel worktree, claude rm conserve plutôt la session et le worktree.

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 WorktreeRemove

En plus des champs d'entrée communs, les hooks WorktreeRemove reçoivent le champ worktree_path, qui est le chemin absolu du 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 encore ensuite, la suppression échoue :

  • Le worktree reste sur le disque, et la commande et le stderr du hook sont envoyés dans le log de débogage.
  • Si vous supprimiez une session en arrière-plan, la session reste également. 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 supprimer à nouveau la session supprime quand même le répertoire.
  • Si claude remote-control nettoyait le worktree d'une session, le terminal qui l'exécute affiche worktree removal failed, kept: <path>.

PreCompact

S'exécute juste avant que Claude Code lance 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, et compaction en cas d'inactivité

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

Bloquer 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 compaction. 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 apparaît et la requête en cours échoue.

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

Entrée 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
}

Sortie de PreCompact

Lorsqu'un hook PreCompact se termine avec le code 0 et ne bloque pas, Claude Code utilise son stdout comme instructions pour le résumé de compaction. Limitez ce stdout à moins de 10 000 caractères par hook. Claude Code coupe une sortie plus longue à la limite et ajoute un marqueur.

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, et après la compaction en cas d'inactivité

Entrée 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 pas de contrôle de décision. Ils ne peuvent pas modifier le résultat de la compaction, mais peuvent effectuer des tâches de suivi.

PreModelSwitch

S'exécute avant que Claude Code applique un changement de modèle que vous ou un client avez demandé. Utilisez-le pour bloquer un changement, exiger une confirmation ou afficher ce que coûtera le changement avant qu'il 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 lorsqu'elle 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 n'atteignent que 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 ID de modèle daté et un ID propre à un fournisseur, comme un ID de modèle Amazon Bedrock, correspondent tous au nom canonique unique vers lequel ils se résolvent, de sorte que claude-opus-5 couvre toutes les écritures d'Opus 5.

Lorsque Claude Code ne peut pas déterminer de nom canonique pour la cible, par exemple un ID 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 | comme claude-opus-4-6|claude-opus-5, ou d'une expression régulière comme .*opus.*. Cet exemple utilise un matcher de nom exact et vérifie également to_model dans l'entrée du hook ; il refuse ainsi un passage à Opus 4.6 en se terminant avec le code 2 et laisse passer toute autre cible :

[tab] macOS/Linux

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"
          }
        ]
      }
    ]
  }
}

[tab] Windows (PowerShell)

Enregistrez un hook de commande qui exécute un script via PowerShell :

{
  "hooks": {
    "PreModelSwitch": [
      {
        "matcher": "claude-opus-4-6",
        "hooks": [
          {
            "type": "command",
            "command": "powershell.exe",
            "args": [
              "-NoProfile",
              "-ExecutionPolicy",
              "Bypass",
              "-File",
              "${CLAUDE_PROJECT_DIR}/.claude/hooks/block-opus-46.ps1"
            ]
          }
        ]
      }
    ]
  }
}

Enregistrez ce script sous .claude/hooks/block-opus-46.ps1 dans votre projet :

$hookInput = [Console]::In.ReadToEnd() | ConvertFrom-Json
if ($hookInput.to_model -match 'opus-4-6') {
  [Console]::Error.WriteLine('Opus 4.6 is retired for this project. Use a newer model.')
  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 signale qu'un hook PreModelSwitch a bloqué le changement, avec votre message comme motif.

Entrée 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 chiffre avant que le changement ait lieu.

Champ Type Description
from_model string ID du modèle à partir duquel le changement s'effectue
to_model string ID 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 nommé dans la demande : un alias tel que opus, un ID 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 fait perdre
cache_ttl string Durée de vie du cache de prompt de la conversation principale au moment de ce changement : "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 l'intégralité du contexte ; considérez donc cette valeur comme une estimation
pricing string Comment Claude Code a calculé 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 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.

Délais d'expiration et autres codes de sortie de PreModelSwitch

Un hook PreModelSwitch qui ne répond pas avant son délai d'expiration bloque le changement. Le délai d'expiration par défaut pour cet événement est de 30 secondes.

Un code de sortie autre que 0 ou 2 sans sortie JSON valide constitue une erreur non bloquante ; le changement a donc lieu, sauf pour les hooks dont l'échec le bloque.

PostModelSwitch

S'exécute après le changement du modèle de la session. Utilisez-le pour donner à Claude des consignes propres au 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 laisse le modèle de la session inchangé.

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 PostModelSwitch

Les hooks PostModelSwitch reçoivent les mêmes champs que PreModelSwitch, avec hook_event_name défini sur "PostModelSwitch" et deux valeurs source supplémentaires : "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 PostModelSwitch

Claude Code prend la sortie stdout en texte brut de votre hook en cas de sortie avec le code 0, ou additionalContext issu de la sortie JSON, et la transmet à Claude avec la requête suivant 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 requête suivante. Consultez Ajouter du contexte pour Claude

Si le hook n'a pas terminé dans les cinq secondes suivant l'envoi de votre prompt suivant, Claude Code envoie cette requête sans la sortie et la joint plutôt à la requête d'après. Si le modèle change plusieurs fois avant la requête suivante, Claude Code ne transmet que 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 de 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 plus. Retirez-le de vos matchers SessionEnd

Entrée 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 pas de 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, comme 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é dans vos fichiers de paramètres, dans la limite de 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 les 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 n'augmentait que le budget global, et un hook sans son propre timeout était toujours annulé au bout de 1,5 seconde.

Elicitation

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

Pour un hook complet avec son entrée de paramètres et son script, consultez Répondre à une demande de formulaire depuis un script.

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

Entrée 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 une é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 une é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 Elicitation

Un hook Elicitation peut répondre à la demande à la place de l'utilisateur, la refuser ou l'annuler, ou la laisser à la boîte de dialogue. Pour répondre, refuser ou annuler, terminez avec le code 0 et affichez un objet hookSpecificOutput avec une action. Le serveur reçoit votre réponse et aucune boîte de dialogue n'apparaît. Chaque ligne de ce tableau indique ce qu'il faut renvoyer pour un résultat donné et ce que reçoit le serveur MCP :

Pour Renvoyer Le serveur reçoit
Répondre à la place de l'utilisateur "action": "accept", avec les valeurs des champs du formulaire dans content accept avec votre content
Refuser la demande "action": "decline" decline
Annuler la demande "action": "cancel" cancel
Laisser la demande à l'utilisateur Aucune sortie, avec le code de sortie 0 La réponse de l'utilisateur depuis la boîte de dialogue

Cette sortie répond à la demande en mode formulaire présentée dans Entrée Elicitation. Les clés de content sont les noms de propriétés du requested_schema de cette demande :

{
  "hookSpecificOutput": {
    "hookEventName": "Elicitation",
    "action": "accept",
    "content": {
      "username": "alice"
    }
  }
}

Cette sortie refuse une demande :

{
  "hookSpecificOutput": {
    "hookEventName": "Elicitation",
    "action": "decline"
  }
}

Dans la boîte de dialogue, sélectionner Decline envoie decline et appuyer sur Esc envoie cancel ; renvoyez donc celui que vous souhaitez que le serveur voie.

Pour une demande en mode URL, un hook qui renvoie accept ignore la boîte de dialogue, de sorte que l'URL ne s'ouvre jamais.

Claude Code ignore reason, systemMessage et continue dans la sortie JSON d'un hook Elicitation, quelle que soit l'action que vous renvoyez.

Autres façons de refuser une élicitation

Votre hook peut également refuser de ces façons. Le serveur reçoit le même decline que pour "action": "decline" :

  • Se terminer avec le code 2 : Claude Code ignore un hookSpecificOutput affiché par le même hook
  • Afficher un "decision": "block" de premier niveau : le blocage l'emporte sur une action dans la même sortie

Lorsque plusieurs hooks correspondent à la même demande, un refus de l'un d'eux l'emporte sur un accept ou un cancel d'un autre.

Ce script refuse les demandes en mode URL et laisse les demandes de formulaire à la boîte de dialogue :

#!/bin/bash
if [ "$(jq -r '.mode')" = "url" ]; then
  exit 2
fi

Ni l'utilisateur ni le serveur ne voient pourquoi votre hook a refusé, car Claude Code n'affiche ni votre stderr ni votre reason.

Claude Code ignorait un decision de premier niveau provenant des hooks Elicitation et ElicitationResult de la v2.1.105 jusqu'au correctif de la v2.1.284.

Répondre à une demande de formulaire depuis un script

Cet exemple répond à une question récurrente à la place de l'utilisateur. Un serveur MCP nommé issue-tracker demande une clé de projet dans un formulaire, et le hook renseigne DOCS. Le script accepte lorsque project_key est l'unique champ du formulaire. Pour toute autre demande, il n'affiche rien, de sorte que la boîte de dialogue apparaît.

[tab] macOS/Linux

Enregistrez un hook de commande pour l'événement dans votre fichier de paramètres, avec le nom du serveur comme matcher :

{
  "hooks": {
    "Elicitation": [
      {
        "matcher": "issue-tracker",
        "hooks": [
          {
            "type": "command",
            "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/answer-project-key.sh",
            "args": []
          }
        ]
      }
    ]
  }
}

Enregistrez ce script sous .claude/hooks/answer-project-key.sh dans votre projet et rendez-le exécutable avec chmod +x :

#!/bin/bash
input=$(cat)
fields=$(jq -c '.requested_schema.properties // {} | keys' <<<"$input")

if [ "$fields" = '["project_key"]' ]; then
  jq -n '{hookSpecificOutput: {hookEventName: "Elicitation", action: "accept", content: {project_key: "DOCS"}}}'
fi

[tab] Windows (PowerShell)

Enregistrez un hook de commande qui exécute le script via PowerShell, avec le nom du serveur comme matcher :

{
  "hooks": {
    "Elicitation": [
      {
        "matcher": "issue-tracker",
        "hooks": [
          {
            "type": "command",
            "command": "powershell.exe",
            "args": [
              "-NoProfile",
              "-ExecutionPolicy",
              "Bypass",
              "-File",
              "${CLAUDE_PROJECT_DIR}/.claude/hooks/answer-project-key.ps1"
            ]
          }
        ]
      }
    ]
  }
}

Enregistrez ce script sous .claude/hooks/answer-project-key.ps1 dans votre projet :

$request = [Console]::In.ReadToEnd() | ConvertFrom-Json
$fields = @($request.requested_schema.properties.PSObject.Properties.Name)

if ($fields.Count -eq 1 -and $fields[0] -eq 'project_key') {
  @{
    hookSpecificOutput = @{
      hookEventName = "Elicitation"
      action = "accept"
      content = @{ project_key = "DOCS" }
    }
  } | ConvertTo-Json -Depth 3
}

Pour vérifier que le hook fonctionne, démarrez Claude Code avec claude --debug et confiez à Claude une tâche qui amène le serveur à demander la clé de projet. Aucune boîte de dialogue n'apparaît, et le log de débogage contient une ligne qui se termine par Elicitation resolved by hook: {"action":"accept","content":{"project_key":"DOCS"}}.

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 soit renvoyée au serveur MCP.

Lorsqu'un hook Elicitation répond à une demande, Claude Code envoie cette réponse au serveur sans exécuter les hooks ElicitationResult.

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

Entrée 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"
}

Sortie ElicitationResult

Un hook ElicitationResult peut laisser passer la réponse de l'utilisateur, en modifier les valeurs ou la bloquer. Pour modifier ou bloquer la réponse, terminez avec le code 0 et affichez un objet hookSpecificOutput avec une action. Chaque ligne de ce tableau indique ce qu'il faut renvoyer pour un résultat donné et ce que reçoit le serveur MCP :

Pour Renvoyer Le serveur reçoit
Laisser passer la réponse Aucune sortie, avec le code de sortie 0 La réponse de l'utilisateur, inchangée
Modifier les valeurs soumises "action": "accept", avec les nouvelles valeurs dans content accept avec votre content à la place des valeurs de l'utilisateur
Bloquer la réponse "action": "decline" decline, sans les valeurs de l'utilisateur
Annuler la demande "action": "cancel" cancel, accompagné des valeurs soumises par l'utilisateur. Pour les retenir, renvoyez "decline"

Cette sortie modifie la réponse présentée dans Entrée ElicitationResult, de sorte que le serveur reçoit alice@example.com là où l'utilisateur a soumis alice :

{
  "hookSpecificOutput": {
    "hookEventName": "ElicitationResult",
    "action": "accept",
    "content": {
      "username": "alice@example.com"
    }
  }
}

Votre content remplace l'intégralité de l'objet content de l'utilisateur ; incluez donc les champs que vous ne modifiez pas. Renvoyez action avec lui, car Claude Code ignore un hookSpecificOutput dépourvu d'action.

Les hooks ElicitationResult s'exécutent également lorsque l'utilisateur refuse ou annule, et votre action remplace la sienne. Vérifiez que l'action de l'entrée vaut accept avant de renvoyer accept, sinon votre hook transforme une demande refusée en demande acceptée. Ce script effectue la même modification lorsque l'utilisateur a accepté, conserve les autres champs et n'affiche rien dans le cas contraire :

#!/bin/bash
input=$(cat)

if [ "$(jq -r '.action' <<<"$input")" = "accept" ]; then
  jq '{hookSpecificOutput: {hookEventName: "ElicitationResult", action: "accept", content: (.content + {username: (.content.username + "@example.com")})}}' <<<"$input"
fi

Cette sortie bloque la réponse :

{
  "hookSpecificOutput": {
    "hookEventName": "ElicitationResult",
    "action": "decline"
  }
}

Le code de sortie 2 et un "decision": "block" de premier niveau bloquent également la réponse. Autres façons de refuser une élicitation explique lequel prend effet lorsqu'un hook les combine, ce que voit l'utilisateur et quelles versions ignoraient decision.

Claude Code ignore reason, systemMessage et continue dans la sortie JSON d'un hook ElicitationResult, quelle que soit l'action que vous renvoyez.

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.

Dans un hook de prompt ou un hook d'agent, vous pouvez rédiger le prompt sous la forme d'une règle indiquant ce qu'il faut bloquer ou autoriser, par exemple « Bloquer toute commande Bash qui lit des fichiers .env », ou sous la forme d'une condition qui doit être remplie, par exemple « Tous les tests unitaires passent ».

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

[!WARNING]

Les hooks d'agent sont expérimentaux. Le comportement et la configuration peuvent changer dans les versions futures. Pour les workflows de production, préférez les hooks de commande.

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, sauf pour la sortie SessionStart répétée. Contrairement au systemMessage d'un hook synchrone, aucun de ces champs ne vous est montré.

Affichez la réponse JSON seule sur stdout ou sur une ligne à part :

  • Seule sur stdout : lorsque la réponse est le seul texte sur stdout, elle peut s'étendre sur plusieurs lignes, comme une sortie jq formatée (pretty-printed). L'extension sur plusieurs lignes nécessite Claude Code v2.1.295 ou ultérieur.
  • Sur une ligne à part : un hook asynchrone peut afficher d'autre texte sur stdout lorsque la réponse tient seule sur une ligne, par exemple avec jq -c.

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

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é.
  • Un hook SessionStart asynchrone s'exécute chaque fois que l'événement se déclenche. Claude Code ignore la sortie du hook lorsque le texte de additionalContext et systemMessage correspond à ce que Claude a reçu en dernier de ce hook, sauf si un hook SessionStart synchrone a ajouté un autre texte depuis. Claude Code livre le texte lorsqu'il change, et à nouveau la prochaine fois que le hook s'exécute après une compaction ou /clear. Avant la v2.1.295, Claude Code ajoutait à nouveau le même texte à la conversation à chaque reprise.

Considérations de sécurité

Avertissement

[!WARNING]

Les hooks de commande exécutent les commandes shell avec vos permissions utilisateur complètes. Ils peuvent modifier, supprimer ou accéder à tous les fichiers auxquels votre compte utilisateur peut accéder. Examinez et testez toutes les commandes de hook avant de les ajouter à votre configuration.

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'"
          }
        ]
      }
    ]
  }
}

Référencer des scripts par chemin dans les hooks PowerShell

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 trouver un hook lent, recherchez dans le journal les lignes Hooks: qui se terminent par une durée. Dans Claude Code v2.1.296 ou ultérieur, chaque hook de type commande sur un événement d'outil, UserPromptSubmit, SessionStart, Stop et plusieurs autres événements en laisse une lorsqu'il se termine, quoi qu'il ait affiché. La ligne indique le nom de l'événement, relié par deux-points au nom de l'outil ou à une autre valeur sur laquelle le hook a été mis en correspondance, puis la commande du hook entre crochets, le plugin dont il provient le cas échéant, la façon dont l'exécution s'est terminée et sa durée, comme dans Hooks: PostToolUse:Write [.claude/hooks/log-write.sh] finished with status 0 (31ms). Une exécution peut aussi se terminer par timed out after <N>ms, cancelled, moved to the background ou failed to start. Sur certains événements, tels que Notification, SessionEnd et PreCompact, un hook de type commande laisse plutôt une ligne completed with status sans durée.

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.