SpyBara
Go Premium

hooks.md 2026-09-08 20:00 UTC to 2026-09-09 22:58 UTC

This page contains 947 additions and 258 deletions.

2026
Wed 9 22:58 Fri 18 23:58 Tue 22 23:59 Fri 25 23:58

Référence des hooks

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

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

Cycle de vie des hooks

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

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

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

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

Event When it fires
SessionStart When a session begins or resumes
Setup When you start Claude Code with --init-only, or with --init or --maintenance in -p mode. For one-time preparation in CI or scripts
UserPromptSubmit When you submit a prompt, before Claude processes it
UserPromptExpansion When a user-typed command expands into a prompt, before it reaches Claude. Can block the expansion
PreToolUse Before a tool call executes. Can block it
PermissionRequest When a tool call needs a permission decision
PermissionDenied When auto mode denies a tool call, including denials without a classifier verdict. Use JSON hookSpecificOutput.retry: true to tell the model it may retry the denied tool call. Claude Code ignores retry when the classifier produced no verdict
PostToolUse After a tool call succeeds
PostToolUseFailure After a tool call fails
PostToolBatch After a full batch of parallel tool calls resolves, before the next model call
Notification When Claude Code sends a notification
MessageDisplay While assistant message text is displayed
SubagentStart When a subagent is spawned
SubagentStop When a subagent finishes
TaskCreated When a task is being created via TaskCreate
TaskCompleted When a task is being marked as completed
Stop When Claude finishes responding
StopFailure When the turn ends due to an API error
TeammateIdle When an agent team teammate is about to go idle
InstructionsLoaded When a CLAUDE.md or .claude/rules/*.md file is loaded into context. Fires at session start and when files are lazily loaded during a session
ConfigChange When a configuration file changes during a session
CwdChanged When the working directory changes, for example when Claude executes a cd command. Useful for reactive environment management with tools like direnv
DirectoryAdded When a working directory is added mid-session via /add-dir or the SDK register_repo_root control request
FileChanged When a watched file changes on disk. The matcher field specifies which filenames to watch
WorktreeCreate When a worktree is being created via --worktree, isolation: "worktree", or for a background session. Replaces default git behavior
WorktreeRemove When a worktree is being removed at session exit, when a subagent finishes, or when you delete a background session
PreCompact Before context compaction
PostCompact After context compaction completes
PreModelSwitch Before Claude Code applies a model switch that you or a client requested. Can block the switch
PostModelSwitch After the session's model changes, including changes Claude Code makes on its own, such as restoring the model when you resume a session
Elicitation When an MCP server requests user input during a tool call
ElicitationResult After a user responds to an MCP elicitation, before the response is sent back to the server
SessionEnd When a session terminates

Comment un hook se résout

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

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

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

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

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

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

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

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

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

L'événement se déclenche

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

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

Le matcher vérifie

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

3

La condition if vérifie

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

4

Le gestionnaire de hook s'exécute

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

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

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

5

Claude Code agit sur le résultat

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

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

Configuration

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

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

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

Emplacements des hooks

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

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

Les sessions cloud sur Claude Code sur le web ne lisent pas votre ~/.claude/settings.json local ; les hooks y proviennent du repo et des paramètres gérés par le serveur de votre organisation. 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 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 d'entreprise peuvent utiliser allowManagedHooksOnly pour restreindre les hooks qui s'exécutent :

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

Consultez ce qui s'exécute sous allowManagedHooksOnly.

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

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

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

Modèles de matcher

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

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

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

Les séparateurs par virgule et la tolérance des espaces blancs environnants nécessitent Claude Code v2.1.191 ou ultérieur.

Les traits d'union dans l'ensemble de correspondance exacte nécessitent Claude Code v2.1.195 ou ultérieur. Sur les versions antérieures, un nom avec trait d'union comme code-reviewer est évalué comme une expression régulière non ancrée, donc il se déclenche également pour senior-code-reviewer ; ancrez-le comme ^code-reviewer$ sur ces versions pour correspondre uniquement à ce nom.

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

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

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

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

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 traits d'union dans l'ensemble de correspondance exacte nécessitent Claude Code v2.1.195 ou ultérieur. Sur les versions antérieures, un préfixe nu avec trait d'union comme mcp__brave-search est évalué comme une expression régulière non ancrée et correspond à chaque outil de ce serveur. La forme mcp__brave-search__.* fonctionne sur chaque version.

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

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

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

Champs du gestionnaire de hook

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

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

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

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

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

Champs communs

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

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

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

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

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

Modèle if Commande Bash Le hook s'exécute-t-il ? Pourquoi
Bash(git *) FOO=bar git push oui les affectations en début sont supprimées ; git push correspond
Bash(git *) npm test && git push oui chaque sous-commande est vérifiée ; git push correspond
Bash(rm *) echo $(rm -rf /) oui les commandes à l'intérieur de $() et des backticks sont vérifiées ; rm -rf / correspond
Bash(rm *) echo $(date) non aucune sous-commande ne correspond à rm *
Bash(cat *) echo before $(date) after non une substitution peut se situer à n'importe quelle position d'argument, donc la commande complète et date sont tous deux vérifiés ; aucun ne correspond à cat *
Bash(git *) $TOOL git push oui Claude Code ne peut pas dire à quoi le nom de la commande se développe, donc il exécute le hook
Bash(git push *) echo $(date) oui les modèles qui spécifient plus que le nom de la commande exécutent le hook de toute façon sur $(), les backticks ou $VAR

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

Champs des hooks de commande

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

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

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

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

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

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

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

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

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

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

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

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

Champs des hooks HTTP

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

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

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

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

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

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

Champs des hooks de l'outil MCP

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

Champ Requis Description
server oui Nom d'un serveur MCP configuré. Pour un serveur fourni par un plugin, c'est le nom limité plugin:<plugin-name>:<server-name>, comme plugin:my-plugin:db, pas la clé de serveur nue. Le serveur doit déjà être connecté ; le hook ne déclenche jamais un flux OAuth ou de connexion
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}"

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 le serveur nommé n'est pas connecté, ou si l'outil retourne isError: true, le hook produit une erreur non-bloquante et l'exécution continue.

Les hooks de l'outil MCP sont disponibles sur chaque événement de hook une fois que Claude Code s'est connecté à vos serveurs MCP. SessionStart et Setup se déclenchent généralement avant que les serveurs ne finissent de se connecter, donc les hooks sur ces événements doivent s'attendre à l'erreur « non connecté » à la première exécution.

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

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 un modèle rapide

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. Change à chaque mise à jour du plugin.
  • ${CLAUDE_PLUGIN_DATA} : le répertoire de données persistantes du plugin, pour les dépendances et l'état qui doivent survivre aux mises à jour du plugin.

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

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

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

Hooks dans les skills et agents

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

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

Tous les événements de hook sont supportés.

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

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

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

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

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

Le menu `/hooks`

Tapez /hooks dans Claude Code pour ouvrir un navigateur en lecture seule pour vos hooks configurés. Le menu affiche chaque événement de hook avec un nombre de hooks configurés, vous permet d'explorer les matchers et affiche les détails complets de chaque gestionnaire de hook. Utilisez-le pour vérifier la configuration, vérifier à partir de quel fichier de paramètres un hook provient ou inspecter la commande, le prompt ou l'URL d'un hook.

Le menu affiche les cinq types de hooks : command, prompt, agent, http et mcp_tool. Chaque hook est étiqueté avec un préfixe [type] et une source indiquant où il a été défini :

  • User Settings : de ~/.claude/settings.json
  • Project Settings : de .claude/settings.json
  • Local Settings : de .claude/settings.local.json
  • Plugin Hooks : du hooks/hooks.json d'un plugin
  • Session Hooks : enregistré en mémoire pour la session actuelle

Sélectionner un hook ouvre une vue détaillée affichant son événement, son matcher, son type, son fichier source et la commande, le prompt ou l'URL complet. Le menu est en lecture seule : pour ajouter, modifier ou supprimer des hooks, éditez directement le JSON des paramètres ou demandez à Claude de faire la modification.

Désactiver ou supprimer les hooks

Pour supprimer un hook, supprimez son entrée du fichier de paramètres JSON.

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

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

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

Entrée et sortie des hooks

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

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

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

Champs d'entrée communs

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

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

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

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

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

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

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

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

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

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

Sortie du code de sortie

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

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

Exit code 0

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

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

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

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

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

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

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

Exit code 2

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

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

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

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

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

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

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

Autres codes de sortie

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

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

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

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

Délais d'expiration

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

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

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

Comportement du code de sortie 2 par événement

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

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

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

Gestion des réponses HTTP

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

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

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

Sortie JSON

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

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

Les chaînes de sortie du hook, y compris additionalContext, systemMessage et stdout brut, sont plafonnées à 10 000 caractères. La sortie qui dépasse cette limite est enregistrée dans un fichier et remplacée par un aperçu et un chemin de fichier, de la même manière que les grands résultats d'outils valides sont gérés sous Output limits.

L'objet JSON supporte trois types de champs :

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

Pour arrêter Claude entièrement :

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

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

Émettre des notifications de terminal

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

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

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

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

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

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

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

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

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

Ajouter du contexte pour Claude

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

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

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

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

Lorsque plusieurs hooks retournent additionalContext pour le même événement, Claude reçoit toutes les valeurs. Si une valeur dépasse 10 000 caractères, Claude Code écrit le texte complet dans un fichier du répertoire de session et transmet à Claude le chemin du fichier avec un court aperçu à la place.

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

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

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

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

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

Contrôle de décision

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

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

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

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

Voici des exemples de chaque modèle en action :

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

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

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

Événements de hook

Chaque événement correspond à un point du cycle de vie de Claude Code où les hooks peuvent s'exécuter. Les sections ci-dessous sont ordonnées pour correspondre au cycle de vie : de la configuration de session à travers la boucle agentique jusqu'à la fin de session. Chaque section décrit quand l'événement se déclenche, quels matchers il supporte, 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 le contexte de développement comme les problèmes existants ou les modifications récentes de votre codebase, ou pour configurer les variables d'environnement. Pour le contexte statique qui ne nécessite pas de script, utilisez CLAUDE.md à la place.

SessionStart s'exécute à chaque session, donc gardez ces hooks rapides. Seuls les hooks type: "command" et type: "mcp_tool" sont supportés.

La valeur du matcher correspond à la façon dont la session a été initiée :

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

Avant v2.1.214, les sessions créées rapportaient la source "resume".

Entrée SessionStart

En plus des champs d'entrée communs, les hooks SessionStart reçoivent source et optionnellement 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 compaction ou "fork" pour une nouvelle session créée à partir 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, donc vérifiez le 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 de session actuel s'il est déjà défini, par exemple via --name ou /rename. Un hook qui émet sessionTitle peut vérifier session_title en premier pour éviter de remplacer un titre que l'utilisateur a défini explicitement

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

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

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

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

Contrôle de décision SessionStart

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

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

Puisque le stdout brut atteint déjà Claude pour cet événement, un hook qui charge uniquement du contexte peut imprimer sur stdout directement sans construire JSON. Utilisez le formulaire JSON lorsque vous avez besoin de combiner le contexte avec d'autres champs tels que sessionTitle.

Utilisez reloadSkills lorsqu'un hook SessionStart installe ou met à jour des skills. La découverte de skills s'exécute normalement avant que les hooks SessionStart se terminent, donc les fichiers que le hook écrit dans ~/.claude/skills/ ou .claude/skills/ n'apparaîtraient autrement que dans la session suivante. Cet exemple synchronise un référentiel de skills partagé et demande la ré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 référentiel est un espace réservé ; remplacez-la par votre propre référentiel de skills. Avec l'espace réservé, le clone échoue et imprime un message fatal: sur stderr. Stderr d'un hook SessionStart qui quitte 0 est informatif uniquement, donc la demande reloadSkills s'applique toujours.

Persister les variables d'environnement

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

Pour définir des variables d'environnement individuelles, écrivez des déclarations 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 tous les changements d'environnement à partir des commandes de configuration, comparez les variables exportées avant et après :

#!/bin/bash

ENV_BEFORE=$(export -p | sort)

# Exécutez vos commandes de configuration qui modifient l'environnement
source ~/.nvm/nvm.sh
nvm use 20

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

exit 0

Setup

Se déclenche uniquement lorsque vous lancez Claude Code avec --init-only, ou avec --init ou --maintenance en mode non-interactif avec le drapeau -p. Il ne se déclenche pas au démarrage normal. Utilisez-le pour l'installation de dépendances ponctuelles ou le nettoyage programmé que vous déclenchez explicitement à partir de CI ou de scripts, séparé du démarrage normal de session. Pour l'initialisation par session, utilisez SessionStart à la place.

La valeur du matcher correspond au drapeau 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 quitte sans démarrer une conversation.

Lorsque vous démarrez ou continuez une conversation avec -p, vous devez également fournir un prompt, en tant qu'argument ou canalisé sur stdin. Vous pouvez ignorer 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'imprime rien au terminal. Pour confirmer que les hooks se sont exécutés, commencez par claude --debug-file <path> --init-only, en remplaçant <path> par un emplacement de fichier journal, et vérifiez le journal pour les entrées de hook Setup et SessionStart.

Parce que Setup ne se déclenche pas à chaque lancement, un plugin qui a besoin d'une dépendance installée ne peut pas compter sur Setup seul. Le modèle pratique est de vérifier la dépendance à la première utilisation et d'installer en cas d'absence, par exemple un hook ou une skill qui teste ${CLAUDE_PLUGIN_DATA}/node_modules et exécute npm install si absent. Consultez le répertoire de données persistantes pour savoir où stocker les dépendances installées. Si vous distribuez votre plugin via une marketplace, vous n'aurez peut-être pas besoin de ce modèle : Claude Code installe automatiquement les dépendances de package Node.js éligibles lorsqu'il met en cache le plugin.

Entrée Setup

En plus des champs d'entrée communs, les hooks Setup reçoivent un champ trigger défini à "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 Setup

Les hooks Setup ne peuvent pas bloquer ; l'exécution continue sur n'importe quel code de sortie. Sur chaque code de sortie, Claude Code rejette les champs de sortie JSON d'un hook Setup, tels que systemMessage, continue et hookSpecificOutput.additionalContext. Avec -p, la sortie, l'erreur et le code de sortie d'un hook Setup n'apparaissent dans la sortie de l'exécution que comme des événements hook_response lorsque vous lancez avec --output-format stream-json --verbose.

Les hooks Setup ont accès à CLAUDE_ENV_FILE. Les variables écrites dans ce fichier persistent dans les commandes Bash suivantes pour la session, tout comme dans les hooks SessionStart. Seuls les hooks type: "command" et type: "mcp_tool" sont supportés.

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 avec impatience et à nouveau plus tard lorsque les fichiers sont chargés avec paresse, par exemple lorsque Claude accède à un sous-répertoire qui contient un CLAUDE.md imbriqué ou lorsque les règles conditionnelles avec le frontmatter paths: correspondent. Le hook ne supporte pas le blocage ou le contrôle de décision. Il s'exécute de manière asynchrone à des fins d'observabilité.

Le matcher s'exécute sur load_reason. Par exemple, utilisez "matcher": "session_start" pour se déclencher uniquement pour les fichiers chargés au démarrage de la session, ou "matcher": "path_glob_match|nested_traversal" pour se déclencher uniquement pour les chargements paresseux.

Entrée InstructionsLoaded

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

Champ Description
file_path Chemin absolu vers le fichier d'instructions qui a été chargé
memory_type Portée du fichier : "User", "Project", "Local" ou "Managed"
load_reason Pourquoi le fichier a été chargé : "session_start", "nested_traversal", "path_glob_match", "include" ou "compact". La valeur "compact" se déclenche lorsque les fichiers d'instructions sont rechargés après un événement de compaction
globs Modèles de glob de chemin du frontmatter paths: du fichier, le cas échéant. Présent uniquement pour les chargements path_glob_match
trigger_file_path Chemin vers le fichier dont l'accès a déclenché ce chargement, pour les chargements paresseux
parent_file_path Chemin vers le 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 InstructionsLoaded

Les hooks InstructionsLoaded n'ont pas de contrôle de décision. Ils ne peuvent pas bloquer ou modifier le chargement des instructions. Claude Code rejette 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 lorsque l'utilisateur soumet un prompt, avant que Claude ne le traite. Cela vous permet d'ajouter du contexte supplémentaire basé sur le prompt/conversation, de valider les prompts ou de bloquer certains types de prompts.

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 le délai par défaut de 600 secondes pour ces types sur d'autres événements. Parce que ce hook s'exécute avant chaque prompt et bloque le traitement du modèle jusqu'à son achèvement, 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.

Apart from a command hook you run with async: true, a UserPromptSubmit command, HTTP, or MCP tool hook that reaches its timeout is canceled and its output, including any additionalContext, is discarded. The prompt still reaches Claude without that context. The transcript shows a notice naming the hook, the timeout that fired, and that the output was discarded.

Un hook de rappel Agent SDK sur UserPromptSubmit qui atteint son délai d'expiration bloque le prompt avec un message nommant le hook et le délai d'expiration, car un rappel là peut agir comme une porte de politique qui ne doit pas échouer ouvertement. La session continue. Avant v2.1.208, un délai d'expiration de rappel sur cet événement terminait le tour avec une erreur d'exécution.

Entrée UserPromptSubmit

En plus des champs d'entrée communs, les hooks UserPromptSubmit reçoivent le champ prompt contenant le texte que l'utilisateur a soumis.

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

Contrôle de décision UserPromptSubmit

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

Il y a deux façons d'ajouter du contexte à la conversation sur exit code 0 :

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

Aucun canal ne produit une entrée de transcription visible. Le stdout brut et la valeur additionalContext sont chacun injectés comme un rappel système qui commence par le nom du hook ; Claude lit les deux. Pour confirmer la livraison, vérifiez le journal de débogage.

Pour bloquer un prompt, retournez un objet JSON avec decision défini à "block" :

Champ Description
decision "block" empêche le prompt d'être traité et l'efface du contexte. Omettez pour autoriser le prompt à procéder
reason Affiché à l'utilisateur lorsque decision est "block". Non ajouté au contexte
additionalContext Chaîne ajoutée au contexte de Claude aux côtés du prompt soumis. Consultez Ajouter du contexte pour Claude
sessionTitle Définit le titre de la session. Utilisez pour nommer les sessions automatiquement en fonction du contenu du prompt
suppressOriginalPrompt Si true lorsque decision est "block", omet le texte du prompt original du message de blocage affiché à l'utilisateur

Un hook qui bloque en quittant 2 s'achemine de la même manière que reason : le message de blocage affiche le texte stderr à l'utilisateur, et il n'est pas ajouté au contexte.

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

UserPromptExpansion

S'exécute lorsqu'une commande slash tapée par l'utilisateur se développe en un prompt avant d'atteindre Claude. Utilisez ceci pour bloquer des commandes spécifiques de l'invocation directe, injecter du contexte pour une skill particulière ou enregistrer quelles commandes les utilisateurs invoquent. Par exemple, un hook correspondant à deploy peut bloquer /deploy sauf si un fichier d'approbation est présent, ou un hook correspondant à une skill de révision peut ajouter la liste de contrôle de révision de l'équipe comme additionalContext.

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

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

Entrée 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 originale. Le champ expansion_type est slash_command pour les skills et commandes personnalisées, ou mcp_prompt pour les prompts du serveur 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 UserPromptExpansion

Les hooks UserPromptExpansion peuvent bloquer l'expansion ou ajouter du contexte. Tous les champs de sortie JSON sont disponibles.

Champ Description
decision "block" empêche la slash command de se développer. Omettez pour autoriser sa progression
reason Affiché à l'utilisateur lorsque decision est "block"
additionalContext Chaîne ajoutée au contexte de Claude aux côtés du prompt développé. Consultez Ajouter du contexte pour Claude

Un hook qui bloque en quittant 2 s'achemine de la même manière 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 d'assistant se diffuse à l'écran. Claude Code affiche le message par incréments : chaque fois qu'un lot de lignes nouvellement complétées est prêt à être rendu, le hook s'exécute une fois avec ces lignes et Claude Code rend le texte de remplacement du hook à leur place. Un long message produit plusieurs appels ; un court message peut ne produire qu'un seul.

Utilisez MessageDisplay pour :

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

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

MessageDisplay est affichage uniquement : le texte de remplacement change uniquement ce qui est rendu à l'écran. La transcription et ce que Claude voit conservent le texte original, donc Claude ne voit jamais le remplacement, et le mode verbeux affiche l'original. Le hook reçoit uniquement le texte du message d'assistant, donc les résultats d'outil et le texte que vous tapez s'affichent inchangés.

MessageDisplay ne supporte pas les matchers et se déclenche pour chaque message d'assistant qui diffuse du texte ; les messages sans texte, comme les réponses d'appel d'outil uniquement, 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 d'assistant au lieu d'une fois par lot de lignes. L'appel unique arrive après que le message se termine et porte le texte du message complet : index est 0, final est true et delta contient le message entier. Un hook qui collecte le texte delta pour chaque message reçoit le même texte total dans les deux modes.

Entrée 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 de lot dépendent de la façon dont le texte se diffuse, donc utilisez index et final pour suivre la progression à travers un message plutôt que de vous attendre à ce que les lignes soient groupées d'une manière particulière.

Champ Description
turn_id UUID du tour actuel
message_id UUID du message d'assistant en cours d'affichage. Stable sur chaque lot du même message. Ce n'est pas l'API msg_… id, donc il ne peut pas être corrélé avec les ids de message de transcription
index Index de base zéro de ce lot dans le message
final true sur le dernier lot du message. Chaque message a exactement un dernier lot
delta Les lignes nouvellement complétées depuis le lot précédent, y compris les sauts de ligne de fin. Toujours des lignes entières, sauf le dernier lot qui peut se terminer au milieu d'une ligne. Dans les exécutions interactives, le delta du dernier lot est vide lorsque le message se termine sur un saut de ligne, donc traitez final, pas un delta non-vide, comme le signal de fin de message. Dans les exécutions Agent SDK et claude -p, l'appel unique porte le message entier
{
  "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 MessageDisplay

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

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

Les hooks MessageDisplay n'ont pas de contrôle de décision. Ils ne peuvent pas bloquer le message ou modifier ce qui est stocké dans la transcription ou envoyé à Claude. Claude Code agit sur displayContent de leur sortie JSON et rejette 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 gras et les backticks de code en ligne du delta, et retourne le résultat comme displayContent.

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

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

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

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

Les lots sans markdown passent inchangés. Si le script échoue, par exemple parce que jq est manquant, Claude Code affiche le texte original et note l'échec uniquement dans la sortie de débogage, pas dans la session.

PreToolUse

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

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

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

Un hook de rappel Agent SDK sur PreToolUse qui dépasse son délai d'expiration bloque l'appel d'outil, et Claude reçoit un résultat d'erreur nommant le délai d'expiration. Un refus explicite retourné par un autre hook a toujours la priorité.

Entrée PreToolUse

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

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

  • Claude Code développe ~ et les chemins relatifs avant que les hooks ne s'exécutent, donc un hook qui correspond aux chemins ne peut pas être contourné via ~ ou une orthographe relative du même chemin
  • Sur Windows, le chemin arrive avec des séparateurs de barre oblique inverse, même lorsque votre hook s'exécute sous Git Bash où $PWD ressemble à /c/project
  • Une comparaison écrite avec des barres obliques avant, comme une vérification /src/, ne correspond jamais à un chemin de barre oblique inverse, et l'appel d'outil procède 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 correspondez à un segment de chemin tel que /src/ plutôt que d'ancrer avec ^, puisque le chemin est absolu

Un appel Write sur Windows livre :

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

Les champs tool_input dépendent de l'outil :

Bash

Exécute les commandes shell.

Champ Type Exemple Description
command string "npm test" La commande shell à exécuter
description string "Run test suite" Description optionnelle de ce que fait la commande
timeout number 120000 Délai d'expiration optionnel en millisecondes. Les valeurs au-dessus du maximum sont réduites au maximum plutôt que rejetées
run_in_background boolean false Si la commande doit s'exécuter en arrière-plan
PowerShell

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

Les champs correspondent à 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 optionnelle de ce que fait la commande
timeout number 120000 Délai d'expiration optionnel en millisecondes
run_in_background boolean false Si la commande doit s'exécuter en arrière-plan

Correspondez à Bash|PowerShell dans les hooks qui inspectent les commandes shell, afin qu'ils couvrent les deux outils :

  • Sur Windows, partout où l'outil PowerShell est activé, Claude traite PowerShell comme le shell principal et achemine les commandes shell à travers lui.
  • Sur Windows sans Git Bash, l'outil est activé automatiquement et Claude Code n'enregistre pas du tout l'outil Bash.
  • Un hook qui correspond uniquement à Bash ne se déclenche jamais là.
Write

Crée ou écrase un fichier.

Champ Type Exemple Description
file_path string "/path/to/file.txt" Chemin absolu vers le 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 vers le fichier à éditer
old_string string "original text" Texte à trouver et remplacer
new_string string "replacement text" Texte de remplacement
replace_all boolean false Si toutes les occurrences doivent être remplacées
Read

Lit le contenu des fichiers.

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

Trouve les fichiers correspondant à un modèle glob.

Champ Type Exemple Description
pattern string "**/*.ts" Modèle glob pour correspondre aux fichiers
path string "/path/to/dir" Répertoire optionnel à rechercher. Par défaut le répertoire de travail courant
Grep

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

Champ Type Exemple Description
pattern string "TODO.*fix" Modèle d'expression régulière à rechercher
path string "/path/to/dir" Fichier ou répertoire optionnel à rechercher
glob string "*.ts" Modèle glob optionnel 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 Activer la correspondance multiligne
WebFetch

Récupère et traite le contenu web.

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

Recherche sur le web.

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

Lance un subagent.

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

Lorsqu'un appel Agent au premier plan se termine, votre hook PostToolUse reçoit le texte final du subagent et la télémétrie d'exécution dans tool_response. Lisez ces champs pour inspecter l'exécution ; pour les cumuls de tokens et de coûts entre les subagents, utilisez les compteurs de tokens et de coûts filtrés sur query_source "subagent", puisque totalTokens et usage couvrent uniquement la demande finale :

Champ Type Exemple Description
status string "completed" "completed" pour les subagents au premier plan, "async_launched" pour les subagents en arrière-plan. À partir de v2.1.198, les subagents s'exécutent en arrière-plan par défaut, donc un run_in_background omis produit également "async_launched"
agentId string "a4d2c8f1e0b3a297" Identifiant pour l'exécution du subagent
content array [{"type": "text", "text": "Found 12 endpoints..."}] Les blocs de texte finaux du subagent
resolvedModel string "claude-sonnet-4-5" Modèle sur lequel le subagent a fonctionné, qui peut différer du modèle demandé. Nécessite Claude Code v2.1.174 ou ultérieur
modelsUsed array ["claude-sonnet-4-5", "claude-haiku-4-5"] Modèles utilisés dans l'ordre, avec les répétitions consécutives effondrées ; défini uniquement lorsque le modèle a été échangé en milieu d'exécution. Nécessite Claude Code v2.1.212 ou ultérieur
totalTokens number 12450 Nombre de tokens de la demande API finale du subagent : tokens d'entrée, de sortie et de cache combinés. Ce n'est pas un total sur toute l'exécution
totalDurationMs number 48211 Durée murale de l'exécution du subagent
totalToolUseCount number 7 Nombre d'appels d'outil que le subagent a effectués
usage object {"input_tokens": 8320, ...} Ventilation des tokens par type de la demande API finale : input_tokens, output_tokens, cache_creation_input_tokens, cache_read_input_tokens

Pour les subagents en arrière-plan, l'outil retourne lorsque la tâche passe en arrière-plan, donc tool_response ne porte aucun champ d'utilisation : un lancement en arrière-plan retourne immédiatement, et une tâche au premier plan que Claude Code met en arrière-plan en milieu d'exécution retourne à cette transition. Il a status: "async_launched", agentId, description, prompt, outputFile et resolvedModel.

Sur une réponse completed, resolvedModel nomme le modèle sur lequel le subagent a commencé, qui peut différer de la valeur model dans tool_input, comme lorsque availableModels ou un autre remplacement s'applique. Il nécessite Claude Code v2.1.174 ou ultérieur. Sur une réponse async_launched, resolvedModel nomme le modèle en usage lorsque l'agent a passé en arrière-plan, donc un échange qui s'est produit avant la mise en arrière-plan est reflété là. modelsUsed et le comportement resolvedModel au moment de la mise en arrière-plan nécessitent Claude Code v2.1.212 ou ultérieur.

AskUserQuestion

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

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

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

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

Dans PostToolUse, tool_response est un objet avec les champs plan et filePath contenant le plan approuvé, plus les drapeaux d'état internes. Lisez tool_response.plan pour le contenu du plan plutôt que de relire le fichier depuis le disque.

Contrôle de décision PreToolUse

Les hooks PreToolUse peuvent contrôler si un appel d'outil procède. Contrairement aux autres hooks qui utilisent un champ decision au niveau supérieur, PreToolUse retourne sa décision à l'intérieur d'un objet hookSpecificOutput. Cela lui donne un contrôle plus riche : quatre résultats (autoriser, refuser, demander ou différer) plus la capacité de modifier l'entrée de l'outil avant l'exécution.

Champ Description
permissionDecision "allow" contourne le dialogue de permission, sauf pour les actions que le mode auto n'approuve pas automatiquement et pour AskUserQuestion et ExitPlanMode, qui ont besoin de updatedInput associé à lui. "deny" empêche l'appel d'outil. "ask" demande à l'utilisateur de confirmer. "defer" sort gracieusement afin que l'outil puisse être repris plus tard. Les règles Deny and ask s'appliquent toujours indépendamment de ce que le hook retourne
permissionDecisionReason Pour "allow" et "ask", affiché à l'utilisateur mais pas à Claude. Pour "deny", affiché à Claude. Pour "defer", ignoré
updatedInput Modifie les paramètres d'entrée de l'outil avant l'exécution. Remplace l'objet d'entrée entier, donc incluez les champs inchangés aux côtés des champs modifiés. Claude Code évalue les règles de permission et l'éligibilité de mise en arrière-plan automatique d'une commande Bash par rapport à l'entrée que votre hook retourne, pas l'entrée que Claude a envoyée. Combinez avec "allow" pour approuver automatiquement, ou "ask" pour montrer l'entrée modifiée à l'utilisateur. Pour "defer", ignoré
additionalContext Chaîne ajoutée au contexte de Claude avant l'exécution de l'outil. Ignoré lorsque permissionDecision est "defer". Consultez Ajouter du contexte pour Claude

Lorsque plusieurs hooks PreToolUse retournent des décisions différentes, la précédence est deny > defer > ask > allow.

Un hook qui bloque en quittant 2 s'achemine de la même manière que "deny" : Claude voit le message stderr comme la raison du refus.

Lorsqu'un hook retourne "ask", le dialogue de permission affiché à l'utilisateur inclut un libellé identifiant d'où provient le hook : [settings] pour un hook de n'importe quel fichier de paramètres ou du frontmatter de l'agent, [plugin:<name>] pour le hook d'un plugin, ou [skill] pour un hook du frontmatter de la skill. Cela aide les utilisateurs à comprendre quelle source de configuration demande une confirmation.

Un "ask" d'un hook force également un dialogue de permission en mode auto : le classificateur peut toujours refuser l'appel d'outil, mais il ne peut pas l'approuver silencieusement. Avant v2.1.211, le classificateur pouvait approuver une commande Bash s'exécutant en dehors du sandbox sans afficher le dialogue que le hook demandait ; le classificateur appliquait toujours ses propres règles de sécurité à cette commande, et un refus de hook était toujours honoré.

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

AskUserQuestion et ExitPlanMode nécessitent une interaction utilisateur et bloquent normalement en mode non-interactif avec le drapeau -p. Retourner permissionDecision: "allow" avec updatedInput satisfait cette exigence : le hook lit l'entrée de l'outil depuis stdin, collecte la réponse via votre propre interface utilisateur et la retourne dans updatedInput afin que l'outil s'exécute sans inviter. Retourner "allow" seul n'est pas suffisant pour ces outils. Pour AskUserQuestion, renvoyez le tableau questions original et ajoutez un objet answers mappant le texte de chaque question à la réponse choisie.

À partir de v2.1.199, un outil MCP dont le serveur le marque avec _meta["anthropic/requiresUserInteraction"] est plus strict : un hook ne peut pas ignorer son dialogue d'approbation avec "allow", avec ou sans updatedInput, car Claude Code ne peut pas confirmer que le hook a collecté l'interaction dont l'outil a besoin.

Différer un appel d'outil pour plus tard

"defer" est pour les 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 en pause Claude à un appel d'outil, de collecter l'entrée via sa propre interface et de reprendre où il s'était arrêté. Claude Code honore cette valeur uniquement en mode non-interactif avec le drapeau -p. Dans les sessions interactives, il enregistre un avertissement et ignore le résultat du hook.

L'outil AskUserQuestion est le cas typique : Claude veut poser une question à l'utilisateur, mais il n'y a pas de terminal pour répondre. Le cycle aller-retour fonctionne comme ceci :

  1. Claude appelle AskUserQuestion. Le hook PreToolUse se déclenche.
  2. Le hook retourne permissionDecision: "defer". L'outil ne s'exécute pas. Le processus quitte avec stop_reason: "tool_deferred" et l'appel d'outil en attente préservé dans la transcription.
  3. Le processus appelant lit deferred_tool_use du résultat 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>. Le même appel d'outil déclenche PreToolUse à nouveau.
  5. Le hook retourne permissionDecision: "allow" avec la réponse dans updatedInput. L'outil s'exécute et Claude continue.

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

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

Il n'y a pas de délai d'expiration ou de limite de tentatives. La session reste sur le disque jusqu'à ce que vous la repreniez, soumise au balayage de rétention cleanupPeriodDays qui supprime les fichiers de session après 30 jours par défaut, suivant les règles du balayage de rétention. Si la réponse n'est pas prête lorsque vous reprenez, le hook peut retourner "defer" à nouveau et le processus quitte de la même manière. Le processus appelant contrôle quand casser la boucle en retournant finalement "allow" ou "deny" du hook.

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

Si l'outil différé n'est plus disponible lorsque vous reprenez, le processus quitte avec stop_reason: "tool_deferred_unavailable" et is_error: true avant que le hook ne se déclenche. Cela se produit lorsqu'un serveur MCP qui a fourni l'outil n'est pas connecté pour la session reprise. La charge utile deferred_tool_use est toujours incluse afin que vous puissiez identifier quel outil a disparu.

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 un dialogue, comme les subagents en arrière-plan en mode non-interactif, Claude Code exécute toujours ces hooks, et si aucun hook ne retourne une décision, il refuse l'appel d'outil. Utilisez Contrôle de décision PermissionRequest pour autoriser ou refuser au nom de l'utilisateur.

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

Claude Code n'exécute pas les hooks PermissionRequest pour la demande réseau d'une commande en sandbox. Pour obtenir un signal pour ce dialogue, utilisez le type de notification permission_prompt.

Correspond au nom de l'outil, mêmes valeurs que 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. Un tableau optionnel permission_suggestions contient les entrées de mise à jour de permission que Claude Code suggère pour cette demande, comme l'ajout d'une règle d'autorisation ou la modification du mode de permission.

Le dialogue de permission construit ses options « toujours autoriser » à partir de ces suggestions, mais le tableau n'est pas une liste exacte des options que vous voyez. Le dialogue peut retenir une option dont la suggestion reste dans le tableau, par exemple lorsque allowManagedPermissionRulesOnly masque les options d'enregistrement de règles. Il peut également offrir des options qui n'ont pas d'entrée de suggestion, comme Oui, et basculer en mode auto, qui change le mode de permission directement plutôt que via une mise à jour de permission.

Les hooks PreToolUse s'exécutent avant chaque appel d'outil, qu'il ait besoin de permission ou non. Les hooks PermissionRequest s'exécutent uniquement lorsque Claude Code est sur le point de vous demander la permission, ou lorsqu'il refuserait autrement un appel qui ne peut pas inviter. Aucun événement 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 retourner un objet decision avec ces champs spécifiques à l'événement :

Champ Description
behavior "allow" accorde la permission, "deny" la refuse. Les règles Deny and ask sont toujours évaluées, donc un hook retournant "allow" ne remplace pas une règle deny 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, donc incluez les champs inchangés aux côtés des champs modifiés. L'entrée modifiée est réévaluée par rapport aux règles deny et ask
updatedPermissions Pour "allow" uniquement : tableau d'entrées de mise à jour de permission à appliquer, comme l'ajout d'une règle d'autorisation ou la modification du mode de permission de session
message Pour "deny" uniquement : indique à Claude pourquoi la permission a été refusée
interrupt Pour "deny" uniquement : si true, arrête Claude

Un hook qui quitte 2 sans un objet decision laisse le flux de permission inchangé, et son stderr est rejeté. Seul l'objet decision peut accorder ou refuser la demande.

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

Entrées de mise à jour de permission

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 a un type qui détermine ses autres champs, et une destination qui contrôle où la modification est écrite.

type Champs Effet
addRules rules, behavior, destination Ajoute des règles de permission. rules est un tableau d'objets {toolName, ruleContent?}. Omettez ruleContent pour correspondre à l'outil entier. behavior est "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 pour default. L'alias manual nécessite Claude Code v2.1.200 ou ultérieur
addDirectories directories, destination Ajoute des répertoires de travail. directories est un tableau de chaînes de chemin
removeDirectories directories, destination Supprime les répertoires de travail

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

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

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

PostToolUse

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

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

Correspondez plus largement lorsque le nom de l'outil n'est pas le bon filtre :

  • Pour exécuter un hook après que n'importe quel outil se termine avec succès, omettez le matcher ou définissez-le à "*". Votre hook peut alors découvrir ce qui a changé lui-même, par exemple en exécutant git status --porcelain, qui liste également les fichiers non suivis que git diff manque. Pour les appels d'outil 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 en dehors de Claude Code réécrit le même fichier.

Entrée PostToolUse

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

{
  "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",
    "success": true
  },
  "tool_use_id": "toolu_01ABC123...",
  "duration_ms": 12
}
Champ Description
duration_ms Optionnel. Temps d'exécution de l'outil en millisecondes. Exclut le temps passé dans les dialogues de permission et les hooks PreToolUse

Contrôle de décision PostToolUse

Les hooks PostToolUse peuvent fournir des commentaires à 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 retourner ces champs spécifiques à l'événement :

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

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

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

Annoter un résultat pour le classificateur du mode auto

Retournez classifierContext pour envoyer une note courte sur le résultat de l'appel d'outil au classificateur du mode auto plutôt qu'à Claude. Le classificateur ne reçoit jamais les résultats des outils eux-mêmes, donc ce champ est la façon supportée de lui dire quelque chose sur ce qu'un appel a retourné avant qu'il examine les actions ultérieures. Le champ nécessite Claude Code v2.1.236 ou ultérieur.

L'exemple ci-dessous indique au classificateur 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 classificateur donne à la note dépend de l'endroit où vous avez configuré le hook :

  • Hooks configurés dans Claude Code : pour les hooks des fichiers de paramètres, des plugins, des skills et du frontmatter de l'agent, le classificateur traite la note comme du contexte non vérifié fourni par l'application. La note n'établit jamais l'intention de l'utilisateur, et si elle prétend que vous avez approuvé ou demandé quelque chose, le classificateur vérifie cette affirmation par rapport à vos propres messages dans la conversation
  • Rappels Agent SDK en processus : lorsqu'une application intégrant Claude Code enregistre le hook comme un rappel SDK TypeScript et retourne la note pendant la session en direct, le classificateur peut peser une déclaration d'utilisateur relayée dans la note comme intention de l'utilisateur. Une telle déclaration peut satisfaire une exigence de consentement que le classificateur 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 qu'une session reprenne, Claude Code traite les notes restaurées comme du contexte non vérifié. Lorsque les hooks des deux groupes annotent le même appel, le classificateur traite la note combinée comme non vérifiée

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

  • Longueur : Claude Code plafonne les notes pour un appel d'outil à 2 000 caractères et tronque le reste. Le plafond est partagé entre chaque hook qui répond à 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 enregistre le résultat de l'outil
  • Appels que le classificateur n'enregistre pas : la transcription du classificateur omet les recherches en lecture seule telles que les lectures de fichiers et les recherches. Claude Code rejette une note attachée à l'un de ces appels
  • Interaction avec les réécritures : lorsque la note décrit la sortie que vous remplacez avec updatedToolOutput, retournez les deux champs dans la même réponse du hook. Claude Code rejette la note si cette réécriture est rejetée ou qu'une réécriture d'un autre hook la remplace. Claude Code livre une note que vous retournez sans réécriture même lorsqu'un autre hook réécrit la sortie

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 retourné un résultat d'erreur. Utilisez ceci pour enregistrer les défaillances, envoyer des alertes ou fournir des commentaires correctifs à Claude.

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

Entrée PostToolUseFailure

Les hooks PostToolUseFailure reçoivent les mêmes champs tool_name et tool_input que PostToolUse, ainsi que les informations d'erreur comme champs au niveau supérieur. Par exemple, une commande npm test échouée pourrait livrer :

{
  "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 optionnel. True lorsque l'échec a atteint Claude Code en tant qu'abandon plutôt qu'en tant qu'erreur que l'outil a signalée. L'annulation d'un outil en cours d'exécution ne déclenche pas ce hook ; le résultat de l'outil porte le message d'interruption à la place
duration_ms Optionnel. Temps d'exécution de l'outil en millisecondes. Exclut le temps passé dans les dialogues 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 échoué de l'outil. 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, pas un format stable.

  • Pour Bash et PowerShell, une commande qui a fonctionné et s'est terminée produit une première ligne Exit code N, puis toute sortie que la commande a produite en tant qu'un bloc avec stdout et stderr entrelacés
  • Une charge utile peut également porter un message d'échec brut sans ligne de code de sortie, lorsque Claude Code n'a pas pu démarrer le processus shell lui-même
  • Claude Code tronque au milieu les chaînes plus longues que 10 000 caractères 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 retourner ces champs spécifiques à l'événement :

Champ Description
additionalContext Chaîne ajoutée au contexte de Claude aux côtés de 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 que chaque appel d'outil dans une batch ait été résolu, avant que Claude Code n'envoie la demande suivante au modèle. PostToolUse se déclenche une fois par outil, ce qui signifie qu'il se déclenche simultanément lorsque Claude fait des appels d'outil parallèles. PostToolBatch se déclenche exactement une fois avec la batch complète, donc c'est le bon endroit pour injecter du contexte qui dépend de l'ensemble des outils qui ont fonctionné plutôt que sur un seul outil. Il n'y a pas de matcher pour cet événement.

Entrée PostToolBatch

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

{
  "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\n     2\t..."
    },
    {
      "tool_name": "Read",
      "tool_input": {"file_path": "/.../ledger/transactions.py"},
      "tool_use_id": "toolu_02...",
      "tool_response": "     1\tfrom __future__ import annotations\n     2\t..."
    }
  ]
}

tool_response contient le même contenu 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 comme l'outil l'a émis. Pour Read, cela signifie du texte préfixé par le numéro de ligne plutôt que le contenu brut du fichier. Les réponses peuvent être volumineuses, donc analysez uniquement les champs dont vous avez besoin.

Contrôle de décision PostToolBatch

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

Champ Description
additionalContext Chaîne de contexte injectée une fois avant l'appel du modèle suivant. Consultez Ajouter du contexte pour Claude pour les détails de livraison, ce qu'il faut y mettre et comment 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."
  }
}

Retourner decision: "block" ou continue: false arrête la boucle agentique avant l'appel du modèle suivant. Le message de blocage provient du JSON reason ou stopReason, ou de stderr sur exit 2. Vous le voyez comme un avertissement dans la transcription, et il reste dans la conversation, donc Claude le voit lorsque la conversation continue.

PermissionDenied

S'exécute lorsque le mode auto refuse un appel d'outil, y compris lorsqu'il refuse sans un verdict du classificateur parce qu'une vérification de sécurité séparée du mode auto a refusé la demande du classificateur ou sa réponse n'a pas analysé. Ce hook ne se déclenche que en mode auto : il ne s'exécute pas lorsque vous refusez manuellement un dialogue de permission, lorsqu'un hook PreToolUse bloque un appel ou lorsqu'une règle deny correspond. Utilisez-le pour enregistrer 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, 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.

{
  "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": "Blocked by classifier"
}
Champ Description
reason La raison du refus : le texte fixe Blocked by classifier dans la plupart des sessions, ou l'explication écrite du classificateur lorsque le modèle du classificateur de la session en fournit une. Pour un refus où une vérification de sécurité séparée du mode auto a refusé la demande du classificateur ou sa réponse n'a pas analysé, la raison commence par Auto mode could not evaluate this action and is blocking it for safety. Pour un refus parce que le modèle du classificateur n'était pas disponible, la raison est le texte fixe Classifier unavailable. Consultez Examiner les refus

Contrôle de décision PermissionDenied

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

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

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

Claude Code ignore retry: true lorsque le classificateur n'a produit aucun verdict sur l'action : sa réponse n'a pas analysé, ou une vérification de sécurité séparée du mode auto a refusé la demande du classificateur. Pour ces refus, Claude Code indique déjà au modèle dans le message de rejet s'il faut réessayer plus tard ou continuer.

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 avec les notifications de bureau désactivées : le paramètre preferredNotifChannel, y compris notifications_disabled, change uniquement comment vous êtes alerté, pas si votre hook s'exécute.

Matcher Quand il se déclenche
permission_prompt Claude a besoin de votre approbation pour un appel d'outil ou la demande réseau d'une commande en sandbox, et le dialogue a attendu environ six secondes
idle_prompt Claude a terminé de répondre il y a environ 60 secondes et vous n'avez pas tapé depuis
auth_success L'authentification se termine
elicitation_dialog Un serveur MCP ouvre un formulaire d'élicitation et vous n'avez pas tapé depuis environ six secondes
elicitation_url_dialog Un serveur MCP vous demande d'ouvrir une URL de navigateur et vous n'avez pas tapé depuis environ six secondes
elicitation_complete Un serveur MCP signale qu'une élicitation en mode URL est complète
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 entrée pendant que la vue agent est ouverte dans un terminal, ou la session actuelle vous pose une question de configuration de terminal d'un coéquipier d'équipe d'agents et vous n'avez pas tapé depuis environ six secondes
agent_completed Une session en arrière-plan se termine ou échoue. Se déclenche uniquement pendant que la vue agent est ouverte dans un terminal
quota_auto_resume_fired Claude Code continue votre tâche après qu'une limite d'utilisation de claude.ai l'ait mise en pause : à la réinitialisation, ou plus tôt lorsque quelque chose que vous faites dans Claude Code pendant l'attente, comme l'ajout de crédits d'utilisation, la mise à niveau de votre plan ou le changement de modèles, rend l'utilisation disponible à nouveau, avec l'exception de paramètre de modèle
quota_auto_resume_stale Une limite d'utilisation de claude.ai s'est réinitialisée pendant que votre ordinateur dormait pendant plus d'environ 30 minutes. Claude Code attend que vous appuyiez sur Enter au lieu de continuer. Après un sommeil plus court, il continue et déclenche quota_auto_resume_fired à la place
quota_auto_resume_disabled Claude Code termine son attente pour une limite d'utilisation de claude.ai sans continuer votre tâche : autoContinueAtUsageLimit s'est désactivé ou la réinitialisation s'est déplacée de plus de 24 heures pendant une attente que Claude Code a commencée seul, la tâche continuée a continué à frapper la limite, ou la continuation 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 Ne pas continuer automatiquement

Les types agent_needs_input et agent_completed nécessitent Claude Code v2.1.198 ou ultérieur.

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

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

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

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

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

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

Utilisez des matchers séparés pour exécuter différents gestionnaires selon le type de notification. Cette configuration déclenche un script d'alerte spécifique à la permission lorsque Claude a besoin d'approbation de permission et une notification différente lorsque Claude a été 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 notification, un title optionnel 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 pas bloquer ou modifier les notifications. Claude Code rejette leurs systemMessage et continue mais émet toujours terminalSequence, sur lequel l'exemple de notification de bureau s'appuie. Les hooks Notification sont destinés aux effets secondaires tels que le transfert de la notification vers un service externe.

SubagentStart

S'exécute lorsqu'un subagent Claude Code est lancé via l'outil Agent. Supporte les matchers pour filtrer par nom de type d'agent. Pour les agents intégrés, c'est le nom de l'agent comme general-purpose, Explore ou Plan. Pour les subagents personnalisés, c'est le champ name du frontmatter de l'agent, pas le nom du fichier.

Pour les subagents fournis par un plugin, l'identifiant de type d'agent est l'identifiant limité au plugin comme my-plugin:reviewer, pas le nom brut du frontmatter. Le deux-points place un nom limité au plugin sur le chemin d'expression régulière, donc ancrez 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 subagent et agent_type avec le nom de l'agent que 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 de subagent, mais ils peuvent injecter du contexte dans le subagent. En plus des champs de sortie JSON disponibles pour tous les hooks, vous pouvez retourner :

Champ Description
additionalContext Chaîne ajoutée au contexte du subagent 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"
  }
}

SubagentStop

S'exécute lorsqu'un subagent Claude Code a terminé sa réponse. Correspond au type d'agent, 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 du matcher. Le transcript_path est la transcription de la session principale, tandis que agent_transcript_path est la propre transcription du subagent stockée dans un dossier subagents/ imbriqué. Le champ last_assistant_message contient le contenu textuel de la réponse finale du subagent, donc les hooks peuvent y accéder sans analyser le fichier de transcription.

Les hooks SubagentStop reçoivent également les tableaux background_tasks et session_crons décrits sous Entrée Stop. Les deux tableaux sont limités à la session parent, pas au subagent.

{
  "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 à "SubagentStop", pour les commentaires sans erreur qui gardent le subagent en cours d'exécution. Retourner decision: "block" avec une reason garde le subagent en cours d'exécution et livre reason au subagent comme sa prochaine instruction. Un hook qui bloque en quittant 2 livre son message stderr de la même manière. Pour injecter du contexte dans la session parent après qu'un subagent retourne, utilisez un hook PostToolUse sur l'outil Agent à la place.

TaskCreated

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

Les hooks TaskCreated ne supportent pas 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 optionnellement 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 créant la tâche. Peut être absent
team_name Dépréciée. Nom d'équipe dérivé de la session ; sera supprimée dans une version future

Contrôle de décision TaskCreated

Un hook TaskCreated peut bloquer la création de deux façons. De l'une ou l'autre façon, Claude Code supprime la tâche et retourne votre message à Claude comme l'erreur de l'outil. Claude Code ignore continue: false de cet événement et Claude continue de travailler.

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

Cet exemple bloque les tâches dont les sujets ne suivent 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 marquée comme complétée. Cela se déclenche dans deux situations : lorsqu'un agent marque explicitement une tâche comme complété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 ceci pour appliquer les critères d'achèvement comme passer les tests ou les vérifications de lint avant qu'une tâche ne puisse se fermer.

Les hooks TaskCompleted ne supportent pas 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 optionnellement 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 de réalisation
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 complétant la tâche. Peut être absent
team_name Dépréciée. Nom d'équipe dérivé de la session ; sera supprimée dans une version future

Contrôle de décision TaskCompleted

Les hooks TaskCompleted supportent deux façons de contrôler l'achèvement de la tâche :

  • Code de sortie 2 : la tâche n'est pas marquée comme complétée et le message stderr est renvoyé au modèle comme commentaire.
  • JSON {"continue": false, "stopReason": "..."} : arrête complètement le coéquipier, correspondant au comportement du hook Stop. Le stopReason est affiché à l'utilisateur.

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')

# Exécutez la suite de tests
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 Claude Code principal a terminé sa réponse. Ne s'exécute pas si l'arrêt s'est produit en raison d'une interruption utilisateur. Les erreurs API déclenchent StopFailure à la place.

Entrée Stop

En plus des champs d'entrée communs, les hooks Stop reçoivent stop_hook_active, last_assistant_message, background_tasks et session_crons. Le champ stop_hook_active est true lorsque Claude Code continue déjà en raison d'un hook stop. Vérifiez cette valeur ou traitez la transcription pour empêcher de bloquer sur une condition qui ne se résoudra jamais. Claude Code remplace le hook et termine le tour après 8 blocages consécutifs.

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

Les tableaux background_tasks et session_crons permettent aux hooks de distinguer « la session est terminée » de « la session est en pause en attente du réveil du travail en arrière-plan ». Les deux tableaux sont présents lorsque le registre des tâches est accessible et sont vides lorsque rien n'est en vol ou programmé.

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

Champ Description
id Identifiant de la tâche
type Étiquette de type de tâche conviviale telle que shell, subagent, monitor, workflow, teammate, cloud session ou MCP task. Chaque étiquette identifie quelle fonctionnalité Claude Code a créé la tâche. Revient au discriminant brut pour les types non reconnus
status Statut actuel de la tâche
description Description en texte libre, limitée à 1 000 caractères avec un marqueur … [+N chars] en chaîne lorsqu'elle est coupée
command Ligne de commande shell, limitée à 1 000 caractères. Présent uniquement pour les tâches shell
agent_type Nom du type de subagent. 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 dans session_crons décrit un réveil programmé 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 le calendrier encode un seul moment de déclenchement, true pour les tâches qui se redéclenchent à chaque correspondance
prompt Prompt soumis lorsque le cron se déclenche, limité à 1 000 caractères avec le même marqueur … [+N chars]

Cet exemple montre une entrée Stop avec une tâche shell en vol 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 retourner ces champs spécifiques à l'événement :

Champ Description
decision "block" empêche Claude de s'arrêter. Omettez pour autoriser Claude à s'arrêter
reason Requis lorsque decision est "block". Indique à Claude pourquoi il doit continuer
hookSpecificOutput.additionalContext Commentaires sans erreur pour Claude. La conversation continue afin que Claude puisse agir dessus, mais contrairement à decision: "block", elle est affichée dans la transcription comme commentaire de hook plutôt qu'une erreur de hook

Un hook qui bloque en quittant 2 s'achemine de la même manière que reason : Claude reçoit le message stderr comme l'explication pour pourquoi 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 conseils à Claude, comme « exécutez la suite de tests avant de terminer ». Cela garde la conversation en cours à travers les mêmes protections de boucle que decision: "block", à savoir l'entrée stop_hook_active et le plafond de 8 continuations consécutives, mais la transcription l'étiquette Stop hook feedback et aucune notification d'erreur de hook n'est affichée :

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

StopFailure

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

Entrée StopFailure

En plus des champs d'entrée communs, les hooks StopFailure reçoivent error, optionnellement error_details et optionnellement last_assistant_message. Le champ error identifie le type d'erreur et est utilisé pour le filtrage du 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 ou unknown
error_details Détails supplémentaires sur l'erreur, le cas échéant
last_assistant_message Le texte d'erreur rendu 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 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 à des fins de notification et de journalisation uniquement.

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 ceci pour appliquer des portes de qualité avant qu'un coéquipier ne cesse de travailler, comme exiger des vérifications de lint réussies ou vérifier que les fichiers de sortie existent.

Les hooks TeammateIdle ne supportent pas 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ée. Nom d'équipe dérivé de la session ; sera supprimée dans une version future

Contrôle de décision TeammateIdle

Les hooks TeammateIdle supportent deux façons de contrôler le comportement du coéquipier :

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

Cet exemple vérifie qu'un artefact de construction existe avant d'autoriser un coéquipier à 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 ceci pour auditer les modifications de paramètres, appliquer les politiques de sécurité ou bloquer les modifications non autorisées aux 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 les exécute uniquement lorsque managed-settings.json ou un fichier dans managed-settings.d/ change. Il applique les paramètres gérés par le serveur et les modifications aux préférences gérées macOS ou à la politique de registre Windows sans les exécuter. Sur WSL avec wslInheritsWindowsSettings, il applique également un fichier de paramètres gérés Windows modifié côté sur son sondage de politique sans les exécuter.

Le matcher filtre sur la source de configuration :

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

Cet exemple enregistre toutes les modifications de configuration pour l'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 optionnellement file_path. Le champ source indique quel type de configuration a changé, et file_path fournit le chemin vers le 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 bloquer 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 d'exécution.

Champ Description
decision "block" empêche la modification de configuration d'être appliquée. Omettez 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, vous pouvez donc les utiliser pour la journalisation d'audit, 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 les paramètres gérés par le serveur arrivent ou se rafraîchissent.

Claude Code agit sur la décision de blocage de la sortie JSON d'un hook ConfigChange et rejette systemMessage et continue. Un changement bloqué ne surface aucun message à vous ou à Claude, que vous bloquez avec reason ou avec stderr sur exit 2. Claude Code écrit uniquement une ligne au journal de débogage.

CwdChanged

S'exécute lorsque le répertoire de travail change pendant une session, par exemple lorsque Claude exécute une commande cd. Utilisez ceci pour réagir aux changements de répertoire : recharger les variables d'environnement, activer les chaînes d'outils spécifiques au projet ou exécuter les scripts de configuration automatiquement. S'associe avec FileChanged pour les outils comme direnv qui gèrent l'environnement par répertoire.

Les hooks CwdChanged ont accès à CLAUDE_ENV_FILE. Les variables écrites dans ce fichier persistent dans les commandes Bash suivantes pour la session, tout comme dans les hooks SessionStart.

CwdChanged ne supporte pas les matchers et se déclenche à chaque changement de répertoire.

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 retourner watchPaths pour définir dynamiquement quels chemins de fichiers FileChanged surveille :

Champ Description
watchPaths Tableau de chemins absolus. Remplace la liste de surveillance dynamique actuelle. Les chemins de votre configuration matcher sont toujours surveillés. Retourner 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 de leur sortie JSON et rejette continue. Dans les sessions interactives, il affiche le systemMessage comme une brève notification de terminal. Le message n'atteint pas le flux de messages SDK.

DirectoryAdded

S'exécute après que vous ajoutiez un répertoire de travail en milieu de session avec la commande /add-dir, ou après qu'un client SDK en ajoute un avec la demande de contrôle register_repo_root. Utilisez ceci pour préparer un référentiel nouvellement ajouté, par exemple en installant ses dépendances.

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

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

Claude Code déclenche DirectoryAdded après avoir rafraîchi l'état du sandbox et de la permission, donc les outils en sandbox voient déjà le nouveau répertoire lorsque votre hook s'exécute. Les commandes du hook elles-mêmes s'exécutent sans 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 Quand il se déclenche
slash_command Vous ajoutez un répertoire avec /add-dir
register_repo_root Un client SDK ajoute un répertoire avec la demande 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 Comment le répertoire a été ajouté, "slash_command" pour /add-dir ou "register_repo_root" pour la demande de contrôle 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 s'est déjà terminé lorsque le hook s'exécute. Claude Code rejette le champ continue de leur sortie JSON et affiche le reste différemment par source :

  • slash_command : Claude Code livre le systemMessage du hook à Claude comme contexte sur le tour de conversation suivant, plutôt que de vous l'afficher. Un nombre de hooks échoués apparaît dans la transcription. La sortie d'échec complète va au journal de débogage
  • register_repo_root : Claude Code écrit la sortie systemMessage et la sortie d'échec au journal de débogage uniquement

FileChanged

S'exécute lorsqu'un fichier surveillé change sur le disque. Claude Code détecte les modifications avec un observateur de système de fichiers, pas en inspectant les appels d'outil, donc il exécute le hook peu importe ce qui a changé le fichier : un appel d'outil Edit ou Write, un script que Claude exécute avec Bash, ou un processus en dehors de Claude Code entièrement. Un usage courant est de recharger les variables d'environnement lorsque les fichiers de configuration du projet changent.

Le matcher pour cet événement sert deux rôles :

  • Construire la liste de surveillance : la valeur est divisée sur | et chaque segment est enregistré comme un nom de fichier littéral dans le répertoire de travail, donc ".envrc|.env" surveille exactement ces deux fichiers. Les modèles regex ne sont pas utiles ici : une valeur comme ^\.env surveillerait un fichier littéralement nommé ^\.env.
  • Filtrer quels hooks s'exécutent : lorsqu'un fichier surveillé change, la même valeur filtre quels groupes de hook s'exécutent en utilisant les règles de matcher standard par rapport au basename du fichier modifié.

Cet exemple normalise les fins de ligne dans data.csv après n'importe quel changement, y compris une commande Bash ou un script externe réécrivant 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 la même chose que perl supprime, un CR à la fin d'une ligne, donc l'exécution après une normalisation quitte sans toucher le fichier. Une garde plus lâche boucle pour toujours, parce que perl -i réécrit le fichier même lorsqu'il ne substitue rien et Claude Code exécute le hook à nouveau après chaque réécriture. Enregistrez ce script à /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 confirmer 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 termine avec des fins de ligne LF.

Pour surveiller les fichiers que vous ne pouvez pas nommer à l'avance, retournez watchPaths d'un hook pour mettre à jour la liste de surveillance dynamiquement. Claude Code démarre l'observateur uniquement lorsque quelque chose nomme un fichier à surveiller, donc semez la liste avec un groupe FileChanged dont le matcher nomme au moins un fichier, ou avec un hook SessionStart ou CwdChanged qui retourne watchPaths. Le matcher filtre toujours quels groupes de hook s'exécutent lorsqu'un fichier surveillé change, donc donnez au groupe qui gère les chemins dynamiques un matcher omis, qui correspond à chaque fichier surveillé et n'ajoute rien à la liste de surveillance. Un matcher "*" correspond également à chaque fichier, mais Claude Code l'enregistre dans la liste de surveillance comme un nom de fichier littéral *.

Les hooks FileChanged ont accès à CLAUDE_ENV_FILE. Les variables écrites dans ce fichier persistent dans les commandes Bash suivantes pour la session, tout comme dans les hooks SessionStart.

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 vers le fichier qui a changé
event Ce qui s'est passé : "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 retourner watchPaths pour mettre à jour dynamiquement quels chemins de fichiers sont surveillés :

Champ Description
watchPaths Tableau de chemins absolus. Remplace la liste de surveillance dynamique actuelle. Les chemins de votre configuration matcher sont toujours surveillés. Utilisez ceci 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 bloquer le changement de fichier de se produire.

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

WorktreeCreate

S'exécute lorsqu'un worktree est en cours de création, soit à partir de claude --worktree, soit à partir d'un subagent utilisant isolation: "worktree", soit pour une session en arrière-plan que Claude Code isole dans son propre worktree. Par défaut, Claude Code crée la copie de travail isolée avec git worktree. Configurer un hook WorktreeCreate remplace ce comportement git par défaut, vous permettant d'utiliser un système de contrôle de version différent comme SVN, Perforce ou Mercurial.

Parce que le hook remplace le comportement par défaut entièrement, .worktreeinclude n'est pas traité. Si vous avez besoin de copier les fichiers de configuration locaux comme .env dans le nouveau worktree, faites-le à l'intérieur de votre script de hook.

Le hook doit retourner 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 retourne le chemin.

Claude Code agit sur le succès du hook et le chemin retourné, et rejette systemMessage et continue.

Cet exemple crée une copie de travail SVN et imprime le chemin pour que Claude Code l'utilise. Remplacez l'URL du référentiel 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 copie fraîche dans un nouveau répertoire et imprime le chemin du répertoire. Le echo sur la dernière ligne est ce que Claude Code lit comme chemin du worktree. Redirigez toute autre sortie du hook 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. C'est un identifiant slug pour le nouveau worktree, soit spécifié par l'utilisateur, soit généré automatiquement, par exemple bold-oak-a3f2.

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

Sortie WorktreeCreate

Les hooks WorktreeCreate n'utilisent pas le modèle de décision autoriser/bloquer standard. Au lieu de cela, le succès ou l'échec du hook détermine le résultat. Le hook doit retourner le chemin du répertoire du worktree créé :

  • Hooks de commande (type: "command") : imprimez le chemin comme la dernière ligne non-vide de stdout. Claude Code supprime les codes d'échappement ANSI avant de lire cette ligne, donc les bannières de démarrage du shell imprimées avant votre echo sont ignorées. Redirigez toute autre sortie du hook vers stderr.
  • Hooks HTTP (type: "http") : retournez { "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 effondrant tout segment . ou .. dans celui-ci. Si le chemin résultant n'est pas un répertoire dans lequel Claude Code peut entrer, la session imprime une erreur nommant le chemin et quitte avec le code 1.

Claude Code refuse un chemin absolu qui contient des segments . ou .., et tout chemin qui passe par un symlink en dessous de la racine du référentiel, parce qu'un symlink engagé au référentiel pourrait rediriger le worktree en dehors de celui-ci. L'erreur nomme le composant rejeté. Retournez un chemin normalisé qui ne passe pas par un symlink à l'intérieur du référentiel. Avant v2.1.216, la création du worktree suivait le chemin du hook sans ce dépistage.

WorktreeRemove

S'exécute lorsqu'un worktree est en cours de suppression. Cela se produit lorsque :

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

Pour les worktrees basés sur git, Claude Code gère le nettoyage automatiquement avec git worktree remove. Si vous avez configuré un hook WorktreeCreate pour un système de contrôle de version non-git, associez-le à un hook WorktreeRemove pour gérer le nettoyage. Sans lui, le répertoire du worktree est laissé sur le disque.

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

Pour une suppression de session en arrière-plan, Claude Code vérifie le chemin du worktree stocké avant d'exécuter le hook et refuse un chemin qui est un symlink ou passe par un en dessous de la racine du référentiel. Le hook s'exécute pour un worktree qui contient toujours des fichiers uniquement lorsque vous confirmez la suppression dans la vue agent ; pour un tel worktree, claude rm garde la session et le worktree à la place. Avant v2.1.216, le hook s'exécutait sur le chemin stocké sans ces vérifications.

Claude Code transmet le chemin que WorktreeCreate a retourné comme 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"
}

Les hooks WorktreeRemove n'ont pas de contrôle de décision. Ils ne peuvent pas bloquer la suppression du worktree mais peuvent effectuer des tâches de nettoyage comme supprimer l'état du contrôle de version ou archiver les modifications. Les défaillances des hooks sont enregistrées en mode debug uniquement.

PreCompact

S'exécute avant que Claude Code ne soit sur le point d'exécuter 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 fenêtre de contexte est pleine

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

Le blocage de la compaction automatique a des effets différents selon le moment où il se déclenche. Si la compaction a été déclenchée de manière proactive avant la limite de contexte, Claude Code la saute et la conversation continue sans compaction. Si la compaction a été déclenchée pour récupérer d'une erreur de limite de contexte déjà retourné par l'API, l'erreur sous-jacente remonte et la demande actuelle échoue.

Claude Code rejette 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 dans /compact et est null lorsqu'il ne transmet rien. Pour auto, custom_instructions est null.

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

PostCompact

S'exécute après que Claude Code complète une opération de compaction. Utilisez cet événement pour réagir au nouvel état compacté, par exemple pour enregistrer le résumé généré ou mettre à jour l'état externe. Claude Code rejette 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 compaction automatique lorsque la fenêtre de contexte est pleine

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 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 affecter le résultat de la compaction mais peuvent effectuer des tâches de suivi.

PreModelSwitch

S'exécute avant que Claude Code n'applique un changement de modèle que vous ou un client avez demandé. Utilisez-le pour bloquer un changement, exiger une confirmation ou afficher le coût du changement avant qu'il ne se produise.

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

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

Claude Code n'exécute pas les hooks PreModelSwitch pour les changements qu'il fait seul, comme un repli de modèle automatique ou la restauration du modèle lorsque vous reprenez une session. Ces changements atteignent PostModelSwitch uniquement.

Claude Code compare le matcher par rapport au nom canonique du modèle vers lequel la session bascule, en ignorant tout suffixe [1m]. Un alias comme opus, un ID de modèle daté et un ID spécifique au fournisseur comme un ID de modèle Amazon Bedrock correspondent tous au nom canonique unique auquel ils se résolvent, donc claude-opus-5 couvre chaque orthographe d'Opus 5.

Lorsque Claude Code ne peut pas déterminer un nom canonique pour la cible, par exemple un ID de modèle personnalisé que seule votre passerelle LLM connaît, il exécute chaque hook PreModelSwitch indépendamment du matcher. Un hook qui bloque doit donc vérifier to_model de son entrée plutôt que de compter uniquement sur le matcher.

Écrivez le matcher comme un nom exact, une liste séparée par | comme claude-opus-4-6|claude-opus-5, ou une expression régulière comme .*opus.*. Cet exemple utilise un matcher de nom exact et vérifie également to_model de l'entrée du hook, donc il refuse un changement vers Opus 4.6 en quittant avec le code 2 et laisse n'importe quelle autre cible passer :

La commande vérifie to_model avec jq :

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

Pour confirmer que le hook fonctionne, exécutez /model claude-opus-4-6 à partir d'une session exécutant un modèle différent. Claude Code garde le modèle actuel et signale qu'un hook PreModelSwitch a bloqué le changement, avec votre message comme raison.

Entrée PreModelSwitch

En plus des champs d'entrée communs, les hooks PreModelSwitch reçoivent les champs du tableau ci-dessous. Les cinq derniers décrivent le coût de l'envoi de la conversation au nouveau modèle, donc un hook peut afficher ce chiffre avant le changement.

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 compare par rapport au nom canonique de ce modèle
requested_model string ou null Le modèle que la demande a nommé : un alias comme opus, un ID de modèle complet, ou null lorsque la demande était pour le modèle par défaut
source string D'où provient 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 demande set_model, ou un changement de modèle dans une demande apply_flag_settings, d'un hôte Agent SDK ou Remote Control
context_tokens number Tokens que la demande suivante renvoie comme son prompt : les tokens d'entrée, de lecture de cache, de création de cache et de sortie de la dernière réponse dans la conversation principale, combinés. 0 avant la première réponse
prompt_cache_warm boolean Si le cache de prompt du modèle actuel est probablement toujours chaud, ce qui signifie que le changement le perd
cache_ttl string Durée de vie du cache de prompt que Claude Code demande pour cette session : "5m" ou "1h"
estimated_cache_write_usd number Coût estimé en dollars américains de l'écriture de context_tokens dans le cache de prompt sur to_model au taux cache_ttl, excluant la réponse suivante. Le serveur n'a peut-être pas besoin de re-cacher le contexte entier, donc traitez-le comme une estimation
pricing string Comment Claude Code a tarifé estimated_cache_write_usd : "configured" à vos propres taux d'organisation lorsqu'elle les a configurés, "catalog" au prix catalogue, ou "default" lorsque to_model n'a pas de prix connu et Claude Code a supposé un taux par défaut

Cet exemple montre l'entrée pour /model opus dans une session exécutant 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 procéder. Le code de sortie 2 ou un decision: "block" au niveau supérieur annule le changement.

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

Champ Description
permissionDecision "allow" procède et saute la confirmation que Claude Code affiche pendant 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 la raison du blocage du changement, ou retourné comme l'erreur pour une demande set_model. Pour "ask", affiché dans le dialogue de confirmation. Ignoré pour "allow"

Seul /model dans une session interactive peut afficher le dialogue "ask". Sur chaque autre surface, y compris le mode non-interactif avec le drapeau -p, /config et les demandes set_model, Claude Code traite "ask" comme un refus.

Cet exemple demande à l'utilisateur de confirmer et cite le nombre de tokens 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 retournent des décisions différentes, la précédence est deny > ask > allow.

Claude Code affiche tout systemMessage que votre hook retourne indépendamment de la décision, donc un hook de rapport de coûts peut retourner {"systemMessage": "..."} et quitter 0.

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

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

PostModelSwitch

S'exécute après que le modèle de la session change. Utilisez-le pour donner à Claude des conseils spécifiques au modèle sans éditer chaque CLAUDE.md, par exemple une instruction à l'échelle de l'organisation qui s'applique sur certains modèles.

PostModelSwitch nécessite Claude Code v2.1.251 ou ultérieur. Il ne peut pas bloquer, parce que le modèle a déjà changé. Claude Code exécute les hooks PostModelSwitch après n'importe lequel de ces changements :

  • Un changement que vous ou un client avez demandé
  • Un repli de modèle automatique, qui change le modèle de la session
  • Un paramètre comme opusplan entrant ou quittant le mode plan
  • Claude Code restaurant le modèle lorsque vous reprenez une session

Claude Code n'exécute pas les hooks PostModelSwitch lorsqu'un modèle d'une chaîne de modèles de repli sert un tour, parce que cette substitution dure un tour et laisse le modèle de la session inchangé.

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

Cet exemple ajoute des conseils chaque fois que le modèle de la session change vers n'importe quel 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 confirmer que le hook fonctionne, basculez vers un modèle Opus à partir d'une session exécutant un modèle différent, par exemple exécutez /model opus à partir d'une session Sonnet, puis demandez à Claude quels conseils il a sur le modèle actuel.

Entrée PostModelSwitch

Les hooks PostModelSwitch reçoivent les mêmes champs que PreModelSwitch, avec hook_event_name défini à "PostModelSwitch" et deux valeurs source supplémentaires : "auto" pour un repli automatique ou un autre changement que Claude Code a fait seul, et "resume" pour le modèle restauré lorsque vous reprenez une session.

requested_model est null lorsque source est "auto". Lorsque source est "resume", c'est le paramètre de modèle sauvegardé que Claude Code a restauré.

Contrôle de décision PostModelSwitch

Claude Code prend votre stdout en texte brut du hook sur exit 0, ou additionalContext de la sortie JSON, et le livre à Claude avec la demande suivante après le changement. En plus des champs de sortie JSON disponibles pour tous les hooks, vous pouvez retourner :

Champ Description
additionalContext Chaîne ajoutée au contexte de Claude avec la demande suivante. Consultez Ajouter du contexte pour Claude

Si le hook n'a pas terminé dans les cinq secondes après que vous ayez envoyé la demande suivante, Claude Code envoie cette demande sans la sortie et l'attache à la demande suivante à la place. Si le modèle change plusieurs fois avant la demande suivante, Claude Code livre uniquement la sortie pour le changement vers le modèle cible final.

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 session. Supporte les matchers pour filtrer par raison de sortie.

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

Raison Description
clear Session effacée avec la commande /clear
resume Session basculée via /resume interactif
logout L'utilisateur s'est déconnecté
prompt_input_exit L'utilisateur a quitté pendant que l'entrée du prompt était visible
other Autres raisons de sortie
bypass_permissions_disabled Supprimé dans v2.1.234 ; Claude Code ne l'envoie pas. Supprimez-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 raisons 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 terminaison de session mais peuvent effectuer des tâches de nettoyage. Claude Code rejette leurs champs de sortie JSON, tels que systemMessage.

Les hooks SessionEnd ont un délai d'expiration par défaut de 1,5 secondes. Cela s'applique à la sortie de session, à /clear et au basculement de sessions via /resume interactif. Si un hook a besoin de plus de temps, définissez un timeout par hook dans la configuration du hook. Le budget global est automatiquement augmenté au délai d'expiration par hook le plus élevé configuré dans les fichiers de paramètres, jusqu'à 60 secondes. Les délais d'expiration définis sur les hooks fournis par les plugins ne relèvent pas le budget. Pour remplacer le budget explicitement, définissez la variable d'environnement CLAUDE_CODE_SESSIONEND_HOOKS_TIMEOUT_MS en millisecondes.

CLAUDE_CODE_SESSIONEND_HOOKS_TIMEOUT_MS=5000 claude

Elicitation

S'exécute lorsqu'un serveur MCP demande une entrée utilisateur en milieu de tâche. Par défaut, Claude Code affiche un dialogue interactif pour que l'utilisateur réponde. Les hooks peuvent intercepter cette demande et répondre par programmation, en ignorant complètement le dialogue.

Le champ matcher correspond 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 optionnels mode, url, elicitation_id et requested_schema.

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

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

Pour l'élicitation en mode URL (authentification basée sur navigateur) :

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

Sortie Elicitation

Pour répondre par programmation sans afficher le dialogue, retournez un objet JSON avec hookSpecificOutput :

{
  "hookSpecificOutput": {
    "hookEventName": "Elicitation",
    "action": "accept",
    "content": {
      "username": "alice"
    }
  }
}
Champ Valeurs Description
action accept, decline, cancel Si accepter, refuser ou annuler la demande
content object Valeurs des champs de formulaire à soumettre. Utilisé uniquement lorsque action est accept

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

Claude Code agit sur hookSpecificOutput de la sortie JSON d'un hook Elicitation et rejette systemMessage et continue.

ElicitationResult

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

Le champ matcher correspond 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 optionnels mode, elicitation_id et content.

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

Sortie ElicitationResult

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

{
  "hookSpecificOutput": {
    "hookEventName": "ElicitationResult",
    "action": "decline",
    "content": {}
  }
}
Champ Valeurs Description
action accept, decline, cancel Remplace l'action de l'utilisateur
content object Remplace les valeurs des champs de formulaire. Significatif uniquement lorsque action est accept

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

Claude Code agit sur hookSpecificOutput de la sortie JSON d'un hook ElicitationResult et rejette systemMessage et continue.

Hooks basés sur des prompts

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

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

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

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. 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, Haiku par défaut
  2. Le LLM répond avec JSON structuré contenant une décision
  3. Claude Code traite automatiquement la décision

Configuration des hooks de prompt

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

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

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

Schéma de réponse

Le LLM doit répondre avec JSON contenant :

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

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

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

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

Vérifier plusieurs conditions avant d'arrêter

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

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

Hooks basés sur des agents

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

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. 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 et aucun champ continueOnBlock :

Champ Requis Description
type oui Doit être "agent"
prompt oui Prompt décrivant ce à vérifier. Utilisez $ARGUMENTS comme placeholder pour l'entrée JSON du hook
model non Modèle à utiliser. Par défaut un modèle rapide
timeout non Délai d'expiration en secondes. Par défaut : 60

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

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

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

Exécuter les hooks en arrière-plan

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

Configurer un hook asynchrone

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

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

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

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

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

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

Comment les hooks asynchrones s'exécutent

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

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

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

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

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

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

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

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

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

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

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

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

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

Limitations

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

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

Considérations de sécurité

Avertissement

Confiance de l'espace de travail

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

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

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

Meilleures pratiques de sécurité

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

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

Outil PowerShell sur Windows

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

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

Pour référencer la racine du projet à partir d'une commande PowerShell en forme shell, écrivez ${CLAUDE_PROJECT_DIR} ou $env:CLAUDE_PROJECT_DIR. À partir de la v2.1.198, 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 une 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.

Avant la v2.1.198, cette réécriture s'appliquait uniquement aux hooks de plugin. Sur les versions antérieures, un hook settings.json a besoin de la forme $env: ou de la forme exec, où ${CLAUDE_PROJECT_DIR} est substitué dans chaque élément args indépendamment de l'endroit où le hook est défini.

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:, qui fonctionne sur chaque version :

{
  "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, y compris les hooks qui ont correspondu, leurs codes de sortie et la sortie complète stdout et stderr, 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:
hook-ran

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

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