← Tous les articles

Les hooks de Claude Code expliqués : la couche déterministe autour de votre agent

Tiré du guide: Claude Code Comprehensive Guide

Que sont les hooks de Claude Code ? Les hooks sont des commandes shell définies par l’utilisateur (auxquelles s’ajoutent des endpoints HTTP, des outils MCP et des prompts de modèle) que Claude Code exécute automatiquement à des points fixes de son cycle de vie : avant un appel d’outil, après une modification, au démarrage de la session, quand Claude termine sa réponse.1 Là où CLAUDE.md donne au modèle des instructions qu’il suivra probablement, les hooks s’exécutent que le modèle coopère ou non. Tapez /hooks dans n’importe quelle session pour voir chaque événement du cycle de vie et ce qui y est branché. {.answer-block}

La plupart des développeurs utilisent Claude Code avec deux couches de contrôle : les permissions, qui filtrent ce que l’agent a le droit de faire, et CLAUDE.md, qui décrit ce qu’il devrait faire. Les hooks forment la troisième couche, et la seule qui garantisse quoi que ce soit. Ci-dessous : le modèle mental, tous les événements du cycle de vie présents dans la documentation actuelle, le contrat exact d’entrée et de sortie, la configuration, cinq patterns opérationnels et un cadre de décision. Chaque détail d’API a été vérifié le 8 août 2026 par rapport à la référence officielle des hooks et au guide — ce système évolue vite, donc en cas de désaccord entre cet article et la référence, c’est la référence qui l’emporte. (Vous découvrez Claude Code ? Commencez par l’installation en 5 minutes ou par le parcours de démarrage.)

En bref : les hooks reçoivent du JSON sur stdin et répondent par des codes de sortie ou du JSON sur stdout. Le code 0 autorise, le code 2 bloque (sur les événements capables de bloquer), et le code 1 — le code d’échec habituel sous Unix — ne bloque rien du tout, ce qui constitue le piège numéro un des hooks.2 Configurez-les dans settings.json sous des noms d’événements comme PreToolUse et Stop, filtrés par des matchers. Servez-vous des hooks pour tout ce qui doit toujours se produire ; servez-vous de CLAUDE.md pour tout ce que le modèle doit simplement savoir.

Le modèle mental : des garanties autour d’un noyau non déterministe

Un agent de codage est un système probabiliste. Demandez-lui de lancer Prettier après chaque modification et il le fera — la plupart du temps. Il peut sauter l’étape quand le changement paraît anodin, quand le contexte s’allonge ou quand votre formulation résonne autrement. CLAUDE.md, les skills et les prompts sont tous des suggestions : de bonne qualité, généralement suivies, jamais garanties.

Les hooks constituent l’enveloppe déterministe autour de ce noyau. Le guide s’ouvre sur une définition d’une seule ligne – « Les hooks sont des commandes shell définies par l’utilisateur. » – et énonce l’objectif sans détour : les hooks apportent un « contrôle déterministe : certaines actions se produisent toujours, au lieu de compter sur le LLM pour choisir de les lancer ».3 (Cette ligne unique du guide sous-estime la surface actuelle ; la définition plus complète de la référence ajoute déjà les endpoints HTTP et les prompts de LLM, et les handlers existent aussi sous forme d’outils MCP – voir la section Configuration plus bas.) Le formateur se déclenche à chaque modification. Le garde-fou de commandes évalue chaque appel Bash. Le verrou de fin contrôle chaque terminaison.

L’application est réelle, pas cosmétique : les hooks PreToolUse se déclenchent avant toute vérification du mode de permission, si bien qu’un hook renvoyant permissionDecision: "deny" bloque l’outil même en mode bypassPermissions ou sous --dangerously-skip-permissions. L’inverse n’est pas vrai — un hook qui renvoie "allow" ne peut pas assouplir une règle de refus définie dans les paramètres. Les hooks peuvent durcir la politique au-delà de ce que les permissions autorisent, jamais l’affaiblir.4

Le cycle de vie : tous les événements de hook

Au 8 août 2026, la référence documente 31 événements de hook.1 Ils se répartissent en trois cadences : une fois par session (SessionStart, SessionEnd), une fois par tour (UserPromptSubmit, Stop, StopFailure) et à chaque appel d’outil dans la boucle agentique (PreToolUse, PostToolUse). Les autres se déclenchent sur des conditions précises — changements de configuration, compaction, sous-agents, interactions MCP.

Événement Se déclenche Un usage concret
SessionStart La session démarre ou reprend Injecter la branche git et les tickets ouverts comme contexte
Setup --init-only, ou --init/--maintenance en mode -p Installer les dépendances en CI avant que l’agent ne démarre
UserPromptSubmit Vous envoyez un prompt, avant que Claude ne le traite Ajouter la date du jour ; rejeter les prompts contenant des secrets
UserPromptExpansion Une commande tapée se transforme en prompt Auditer ou refuser les expansions de skills et de commandes
PreToolUse Avant l’exécution d’un appel d’outil Bloquer les commandes shell destructrices
PermissionRequest Une boîte de dialogue de permission apparaît Approuver automatiquement les commandes de confiance pour ne plus être sollicité
PermissionDenied Le classifieur du mode auto refuse un appel d’outil Renvoyer retry: true pour que le modèle puisse réessayer
PostToolUse Après la réussite d’un appel d’outil Formater automatiquement chaque fichier modifié
PostToolUseFailure Après l’échec d’un appel d’outil Journaliser les commandes en échec pour le tri
PostToolBatch Après un lot d’appels d’outils parallèles, avant l’appel de modèle suivant Poser un point de reprise ou stopper la boucle agentique
Notification Claude Code envoie une notification Alerte sur le bureau quand Claude attend une réponse
MessageDisplay Pendant l’affichage du texte d’un message de l’assistant Caviarder à l’écran (affichage seul ; la transcription reste intacte)
SubagentStart Un sous-agent est lancé Injecter du contexte propre au type d’agent
SubagentStop Un sous-agent termine Valider la sortie du sous-agent avant qu’elle ne remonte
TaskCreated Une tâche est créée via TaskCreate Faire respecter les règles de nommage ou de périmètre des tâches
TaskCompleted Une tâche passe au statut terminé Vérifier les critères d’acceptation avant que la clôture ne tienne
Stop Claude termine sa réponse Verrou de fin : empêcher la clôture tant que les tests échouent
StopFailure Le tour s’achève sur une erreur d’API Alerter sur rate_limit ou billing_error (journalisation seule ; sortie ignorée)
TeammateIdle Un coéquipier d’une équipe d’agents va passer en veille Garder les coéquipiers occupés grâce à une file d’attente
InstructionsLoaded Un fichier CLAUDE.md ou .claude/rules/*.md entre dans le contexte Journaliser quelles instructions sont entrées dans la session
ConfigChange Un fichier de configuration change en cours de session Bloquer les modifications non autorisées des paramètres
CwdChanged Le répertoire de travail change Recharger les environnements façon direnv
DirectoryAdded Un répertoire de travail est enregistré en cours de session via /add-dir ou via register_repo_root du SDK (v2.1.219+) Charger le contexte de ce dépôt dès qu’il rejoint la session
FileChanged Un fichier surveillé change sur le disque Rafraîchir les variables d’environnement quand .env change
WorktreeCreate Un worktree est créé via --worktree ou isolation: "worktree" Remplacer le provisionnement par défaut des worktrees git
WorktreeRemove Un worktree est supprimé Nettoyage sur mesure à la fin de la session ou du sous-agent
PreCompact Avant la compaction du contexte Sauvegarder un état que vous ne pouvez pas perdre
PostCompact Une fois la compaction terminée Réinjecter le contexte critique
Elicitation Un serveur MCP réclame une saisie utilisateur Remplir automatiquement les formulaires en exécution headless
ElicitationResult Après votre réponse à une sollicitation MCP Valider ou remplacer la réponse avant qu’elle ne remonte
SessionEnd La session se termine Archiver les journaux, libérer les ressources

Vous n’aurez pas besoin de la plupart d’entre eux. Presque toute configuration de production repose sur cinq : PreToolUse, PostToolUse, UserPromptSubmit, SessionStart et Stop. Les autres existent pour le jour où vous en aurez besoin.

Le contrat : du JSON en entrée, des codes de sortie ou du JSON en sortie

Les hooks de type commande reçoivent du JSON sur stdin et répondent via les codes de sortie, stdout et stderr. (Les hooks HTTP reçoivent le même JSON dans le corps d’une requête POST et répondent via le corps de la réponse.)2

Chaque événement livre une enveloppe commune — session_id, transcript_path, cwd et hook_event_name, avec permission_mode sur la plupart des événements — à laquelle s’ajoutent des champs propres à l’événement. Un hook PreToolUse pour une commande Bash reçoit :

{
  "session_id": "abc123",
  "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" }
}

D’autres événements changent la fin : UserPromptSubmit porte prompt, SessionStart porte source (startup/resume/clear/compact/fork – la cinquième valeur est arrivée avec les sessions forkées en v2.1.214, et un hook qui filtre sur source recopié depuis une ancienne liste de quatre valeurs ratera silencieusement les forks), Stop porte stop_hook_active et last_assistant_message. Les hooks déclenchés à l’intérieur de sous-agents reçoivent en plus agent_id et agent_type.2

Codes de sortie

Trois issues :2

  • Code 0 — succès. Claude Code analyse stdout à la recherche de champs de sortie JSON. Pour la plupart des événements, stdout ne va que dans le journal de débogage ; pour UserPromptSubmit, UserPromptExpansion et SessionStart, un stdout brut est ajouté au contexte visible par Claude.
  • Code 2 — erreur bloquante. Stdout (y compris tout JSON) est ignoré ; stderr est renvoyé à Claude comme message d’erreur. Ce que « bloquer » signifie dépend de l’événement.
  • Tout autre code de sortie — erreur non bloquante. La transcription affiche un avis <hook name> hook error, et l’exécution continue.

Cette dernière ligne mérite le gras : le code 1 ne bloque rien. La documentation prévient directement — Claude Code traite le code 1 comme une erreur non bloquante et poursuit, alors même que 1 est le code d’échec habituel sous Unix. Les hooks de politique doivent faire exit 2.2

Ce que fait le code 2, événement par événement :2

Événement Effet du code 2
PreToolUse Bloque l’appel d’outil
PermissionRequest Refuse la permission
UserPromptSubmit Bloque le traitement et efface le prompt
UserPromptExpansion Bloque l’expansion
Stop / SubagentStop Empêche l’arrêt ; la conversation continue
TeammateIdle Empêche le coéquipier de passer en veille
TaskCreated / TaskCompleted Annule la création / empêche la clôture
ConfigChange Bloque le changement de configuration (sauf policy_settings)
PreCompact Bloque la compaction
PostToolBatch Arrête la boucle agentique avant l’appel de modèle suivant
Elicitation / ElicitationResult Refuse la sollicitation / transforme la réponse en refus
WorktreeCreate Tout code de sortie non nul annule la création du worktree

Tout le reste est incapable de bloquer. PostToolUse et PostToolUseFailure montrent stderr à Claude (l’outil a déjà tourné) ; SessionStart, Notification, SessionEnd, CwdChanged, FileChanged, PostCompact, SubagentStart et Setup ne montrent stderr qu’à l’utilisateur, tandis que DirectoryAdded n’envoie stderr que dans le journal de débogage ; StopFailure, InstructionsLoaded, MessageDisplay et PermissionDenied ignorent le code de sortie — pour PermissionDenied, le seul levier est le champ JSON retry: true.2

Sortie JSON

Pour un contrôle plus fin que bloquer-ou-se-taire, sortez avec le code 0 et affichez un objet JSON sur stdout. Une règle d’emblée : des codes de sortie ou du JSON, jamais les deux — le JSON n’est traité qu’avec le code 0, et le code 2 le jette.5

Des champs universels fonctionnent sur tous les événements : continue: false arrête complètement Claude (avec stopReason affiché à l’utilisateur), suppressOutput masque stdout dans la transcription, systemMessage affiche un avertissement à l’utilisateur, et terminalSequence émet une séquence d’échappement de terminal autorisée (notification bureau, titre de fenêtre, bip). Les champs de décision, eux, dépendent de l’événement :5

Événements Schéma de décision Champs clés
UserPromptSubmit, UserPromptExpansion, PostToolUse, PostToolUseFailure, PostToolBatch, Stop, SubagentStop, ConfigChange, PreCompact decision de premier niveau decision: "block" + reason (montré à Claude). Omettez decision pour autoriser
PreToolUse hookSpecificOutput permissionDecision : "allow" | "deny" | "ask" | "defer", plus permissionDecisionReason et updatedInput pour réécrire les arguments de l’outil avant exécution
PermissionRequest hookSpecificOutput decision.behavior : "allow" | "deny", avec decision.updatedInput en option
PermissionDenied hookSpecificOutput retry: true indique au modèle qu’il peut réessayer
PostToolUse hookSpecificOutput updatedToolOutput remplace le résultat de l’outil
Stop / SubagentStop hookSpecificOutput additionalContext : un retour sans caractère d’erreur qui prolonge la conversation sans compter comme une erreur de hook
SessionStart, Setup, SubagentStart Contexte uniquement additionalContext, plus initialUserMessage, sessionTitle, watchPaths, reloadSkills réservés à SessionStart. Aucun blocage
MessageDisplay hookSpecificOutput displayContent ne remplace que le texte affiché à l’écran
Elicitation / ElicitationResult hookSpecificOutput action : "accept" | "decline" | "cancel", plus content
TeammateIdle, TaskCreated, TaskCompleted continue universel continue: false + stopReason interrompt complètement le flux du coéquipier ou de la tâche (le code 2 reste le blocage propre à l’événement)
WorktreeCreate Renvoi d’un chemin Les hooks de type commande affichent le chemin du worktree sur stdout ; les hooks HTTP renvoient hookSpecificOutput.worktreePath ; un échec ou un chemin manquant fait échouer la création
WorktreeRemove, Notification, SessionEnd, PostCompact, InstructionsLoaded, StopFailure, CwdChanged, DirectoryAdded, FileChanged Aucun Effets de bord uniquement

Deux détails qui piègent. D’abord, PreToolUse fait exception au schéma du decision de premier niveau : il utilisait historiquement decision/reason à ce niveau, mais ces champs y sont dépréciés ("approve"/"block" correspondent à "allow"/"deny") ; utilisez hookSpecificOutput.permissionDecision.5 Ensuite, quand plusieurs hooks PreToolUse se contredisent, la priorité est deny > defer > ask > allow – la réponse la plus restrictive gagne. Malgré tout, confiez chaque décision à un hook et un seul plutôt que de vous reposer sur cet arbitrage.5

Configuration : settings.json, matchers, portée

La configuration des hooks s’emboîte sur trois niveaux : choisissez un événement, ajoutez un groupe de matchers pour filtrer les cas où il se déclenche, puis définissez un ou plusieurs handlers de hook à exécuter.6

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

L’endroit où vous placez cela détermine la portée : ~/.claude/settings.json s’applique à tous vos projets, .claude/settings.json est limité au projet et versionnable, .claude/settings.local.json est limité au projet et ignoré par git, et la précédence habituelle des paramètres s’applique — politique gérée avant local, avant projet, avant utilisateur.9 Les hooks peuvent aussi être livrés dans des plugins (hooks/hooks.json) ainsi que dans le frontmatter d’un skill ou d’un agent, et les administrateurs d’entreprise peuvent imposer des hooks gérés que les utilisateurs ne peuvent pas outrepasser.6

Les matchers sont évalués d’après leurs caractères : "*", "" ou un matcher omis correspond à tout ; une valeur qui ne contient que des lettres, des chiffres, _, -, des espaces, des virgules et | est une chaîne exacte ou une liste (Bash, Edit|Write) ; tout le reste devient une expression régulière JavaScript non ancrée, si bien que Edit.* correspond à la fois à Edit et à NotebookEdit — ancrez avec ^Edit$ quand vous visez un seul outil. Les matchers distinguent la casse, et chaque événement filtre sur son propre champ : le nom de l’outil pour les événements d’outils, source pour SessionStart, le type d’agent pour SubagentStart, le type de notification pour Notification.6 Pour un filtrage plus fin sur les événements d’outils, le champ if de chaque handler accepte une règle de permission comme "Bash(git *)" — mais il fonctionne au mieux de ses possibilités (il s’ouvre en cas de commande non analysable), alors utilisez des règles de permission, et non if, pour des garanties strictes.6 Un changement de sémantique à connaître : depuis la v2.1.214, un motif de chemin à un seul segment dans if (comme Edit(src/**)) ne correspond qu’à un src de premier niveau sous le répertoire de travail ; un if écrit avant cette version cesse discrètement de correspondre aux chemins imbriqués comme packages/app/src/ – écrivez Edit(**/src/**) pour retrouver le comportement à profondeur libre.6

Les handlers existent en cinq types : command (shell), http (endpoint POST), mcp_tool, prompt (évaluation du modèle en un tour) et agent (un sous-agent avec accès à Read/Grep/Glob ; expérimental). Délais d’expiration par défaut : 600 secondes pour command/http/mcp_tool (abaissés à 30 pour UserPromptSubmit et à 10 pour MessageDisplay), 30 pour prompt, 60 pour agent — modifiables hook par hook avec timeout.6 Tous les hooks correspondants s’exécutent en parallèle, les handlers identiques étant dédoublonnés, et $CLAUDE_PROJECT_DIR pointe vos scripts vers la racine du projet.

Vérifiez avec /hooks : un explorateur en lecture seule qui montre chaque événement, ses hooks configurés et le fichier de paramètres dont chacun provient. Pour changer quoi que ce soit, modifiez le JSON (ou demandez-le à Claude). Pour tout neutraliser temporairement, mettez "disableAllHooks": true.6

Cinq patterns

Génériques et minimaux. Le tutoriel sur les hooks construit des versions de production plus complètes de plusieurs d’entre eux, et Hooks pour le développement Apple les applique à la chaîne d’outils iOS.

1. Formatage automatique après les modifications (PostToolUse)

Tiré directement du guide officiel — chaque fichier que Claude touche est formaté, sans exception :3

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          { "type": "command", "command": "jq -r '.tool_input.file_path' | xargs npx prettier --write" }
        ]
      }
    ]
  }
}

Remplacez la commande par ruff format, gofmt ou swiftformat selon les exigences de votre stack.

2. Bloquer les commandes dangereuses (PreToolUse, code 2)

#!/bin/bash
# .claude/hooks/guard-bash.sh — register on PreToolUse, matcher "Bash"
command=$(jq -r '.tool_input.command // empty')
case "$command" in
  *"rm -rf"* | *"git push --force"* | *"DROP TABLE"*)
    echo "Blocked: matches a destructive pattern. Propose a safer alternative." >&2
    exit 2 ;;
esac
exit 0

Le code 2 bloque l’appel et renvoie stderr à Claude, qui corrige sa trajectoire au lieu de réessayer à l’aveugle. L’équivalent JSON — permissionDecision: "deny" accompagné d’une raison — fait la même chose, avec la possibilité d’évoluer vers "ask" (escalade vers l’humain) ou updatedInput (réécriture de la commande).5

3. Injecter du contexte au démarrage de la session (SessionStart)

Un stdout brut venu d’un hook SessionStart devient du contexte visible par Claude — aucun JSON requis :1

#!/bin/bash
# .claude/hooks/session-context.sh — register on SessionStart
echo "Current branch: $(git branch --show-current)"
echo "Recent commits:"
git log --oneline -5
echo "Uncommitted files: $(git status --porcelain | wc -l | tr -d ' ')"
exit 0

Réservez cela à l’état dynamique. Les conventions statiques ont leur place dans CLAUDE.md, ce que la documentation elle-même recommande pour le contexte qui ne nécessite pas de script.1

4. Un verrou de fin sur Stop

Stop se déclenche quand Claude termine sa réponse. Le bloquer force l’agent à continuer de travailler jusqu’à ce qu’une condition soit remplie :

#!/bin/bash
# .claude/hooks/stop-gate.sh — register on Stop
input=$(cat)
if [ "$(echo "$input" | jq -r '.stop_hook_active')" = "true" ]; then
  exit 0  # already continuing because of this hook; don't loop forever
fi
if ! npm test --silent > /tmp/stop-gate.log 2>&1; then
  jq -n '{decision: "block", reason: "Tests are failing. Fix them before finishing. Log: /tmp/stop-gate.log"}'
fi
exit 0

La vérification de stop_hook_active compte : Claude Code plafonne par défaut un hook Stop à 8 blocages consécutifs (relevable via CLAUDE_CODE_STOP_HOOK_BLOCK_CAP), et un verrou qui ne vérifie jamais s’il a lui-même provoqué la relance les brûlera tous d’affilée.7 Pour un pilotage plus doux, renvoyez hookSpecificOutput.additionalContext au lieu de decision: "block" — même relance, mais sous forme de retour identifié plutôt que d’erreur de hook. Et pour les conditions ponctuelles, la commande intégrée /goal est un hook Stop basé sur un prompt, limité à la session et sans aucune configuration.1

5. Le dispatcher : un point d’entrée, beaucoup de petits hooks

Enregistrer dix hooks, c’est dix entrées dans settings.json qui divergent d’une machine et d’un projet à l’autre. L’alternative : enregistrer un dispatcher par événement et router par convention.

#!/bin/bash
# .claude/hooks/dispatch.sh — register once per event you care about
input=$(cat)
event=$(echo "$input" | jq -r '.hook_event_name')
dir="$CLAUDE_PROJECT_DIR/.claude/hooks/$event"
[ -d "$dir" ] || exit 0
for hook in "$dir"/*.sh; do
  [ -x "$hook" ] || continue
  echo "$input" | "$hook" || exit $?
done
exit 0

Ajouter un garde-fou revient désormais à faire chmod +x sur un nouveau fichier dans .claude/hooks/PreToolUse/ — settings.json ne change jamais, chaque script reste assez court pour être testé isolément, et le premier code 2 se propage. Une réserve : le dispatcher sérialise ce que Claude Code exécuterait en parallèle, et il convient surtout aux hooks à codes de sortie — un hook qui émet du JSON devrait rester autonome, puisque stdout doit contenir exactement un objet JSON.5

Hook, CLAUDE.md, skill ou mémoire

Quatre mécanismes, quatre rôles :

Mécanisme Rôle Règle de choix
Hook Application Si le sauter doit être impossible — formatage, sécurité, verrous — c’est un hook
CLAUDE.md Orientation Si c’est une convention que le modèle doit connaître à chaque session — stack, style, commandes — c’est CLAUDE.md
Skill Capacité Si c’est une procédure avec ses propres instructions et scripts, invoquée quand elle est pertinente, c’est un skill
Mémoire Rappel Si c’est un fait appris au cours d’une session dont les sessions futures ont besoin, c’est la mémoire

Le mode d’échec joue dans les deux sens. Encoder des conventions sous forme de hooks vous vaut des scripts fragiles qui imposent ce qu’une phrase d’orientation gérerait très bien. Encoder une politique en prose dans CLAUDE.md vous vaut un agent qui fait un force-push sur main le jour précis où cela compte. Le test : que coûte le fait que le modèle ignore cela une fois ? Agacement → CLAUDE.md. Incident → hook.

Ce que les hooks ne savent pas faire

Des limites assumées, toutes tirées de la documentation officielle :7

  • Les hooks ne peuvent appeler ni outils ni commandes slash. Les hooks de type commande parlent stdout, stderr et codes de sortie — rien d’autre. Le contexte renvoyé via additionalContext est injecté en texte brut.
  • PostToolUse ne peut rien annuler. L’outil a déjà tourné. La prévention se joue dans PreToolUse.
  • Stop se déclenche à la fin de chaque réponse, pas seulement à « tâche terminée », et jamais sur une interruption de l’utilisateur (les erreurs d’API déclenchent StopFailure à la place). La logique du verrou doit tolérer les arrêts en pleine tâche.
  • PermissionRequest ne se déclenche pas dans une exécution headless (-p) simple. Il se déclenche bien sous -p quand un callback canUseTool de l’Agent SDK fournit la demande, ainsi que pour les appels d’outils des sous-agents en arrière-plan ; pour tout le reste de l’automatisation, prenez PreToolUse.
  • PreToolUse ne voit pas les fichiers référencés par @. Les fichiers tirés par @ dans votre prompt n’impliquent aucun appel d’outil ; protégez les chemins de cette voie avec des règles de refus sur Read.1
  • Un updatedInput parallèle n’est pas fiable, par conception. Quand plusieurs hooks PreToolUse réécrivent les arguments du même outil, une seule réécriture survit et vous ne choisissez pas laquelle. Confiez chaque réécriture à un hook unique.
  • Les délais d’expiration annulent le hook. 600 secondes par défaut pour les hooks de type commande (30 pour UserPromptSubmit, 10 pour MessageDisplay) ; un verrou trop lent qui expire est un verrou qui n’a pas tourné.
  • La sortie est plafonnée à 10 000 caractères — le dépassement est écrit dans un fichier et remplacé par un aperçu.
  • Les hooks s’exécutent avec l’intégralité de vos droits utilisateur. L’avertissement de la référence elle-même : ils « peuvent modifier, supprimer ou consulter tous les fichiers accessibles à votre compte utilisateur. Relisez et testez toutes les commandes de hook avant de les ajouter à votre configuration ».8 Mettez vos variables entre guillemets, utilisez des chemins absolus, laissez de côté les fichiers sensibles.
  • Un hook cassé dégrade toutes les sessions jusqu’à sa réparation. Déboguez avec la vue de transcription (Ctrl+O), avec claude --debug-file /tmp/claude.log ou avec /debug en cours de session ; un grand classique est un profil shell qui écrit au démarrage et corrompt la sortie JSON de votre hook.7

Questions

Que sont les hooks de Claude Code ?

Les hooks sont des commandes définies par l’utilisateur — scripts shell, endpoints HTTP, outils MCP ou prompts de modèle — que Claude Code exécute automatiquement à des points précis du cycle de vie.3 Ils reçoivent le JSON de l’événement sur stdin et répondent par des codes de sortie ou du JSON : bloquer un appel d’outil, injecter du contexte, réécrire des arguments, garder l’agent au travail. Contrairement aux instructions de CLAUDE.md, ils s’exécutent à chaque fois, quel que soit le comportement du modèle.

Quelle différence entre les hooks PreToolUse et les permissions ?

Les règles de permission sont déclaratives : des motifs statiques d’autorisation, de refus ou de demande que Claude Code évalue lui-même. Les hooks PreToolUse, eux, sont programmables : votre code inspecte l’entrée complète de l’outil et tranche. Les hooks se déclenchent avant les vérifications du mode de permission, donc un "deny" venu d’un hook tient même en mode bypassPermissions — mais un "allow" venu d’un hook ne peut pas passer outre une règle de refus des paramètres.4 Utilisez des règles de permission pour tout ce qu’un motif sait exprimer ; sortez un hook quand la décision réclame de la logique, un état externe ou une réécriture de l’entrée.

Les hooks fonctionnent-ils en mode headless (-p) ?

Oui — avec une nuance : les hooks PermissionRequest sautent les exécutions -p simples (rien n’y fournit de demande de permission), alors qu’ils se déclenchent bien lorsqu’un callback canUseTool de l’Agent SDK en fournit une, ainsi que pour les appels d’outils des sous-agents en arrière-plan. Les décisions de permission automatisées pour les exécutions headless simples relèvent de PreToolUse.7 Le mode headless débloque en outre une option que les sessions interactives ignorent : permissionDecision: "defer", qui met un appel d’outil en pause pour qu’un processus englobant (une application bâtie sur l’Agent SDK, une interface maison) recueille une saisie et reprenne la session plus tard.5

Pourquoi mon hook s’exécute-t-il sans rien bloquer ?

Presque toujours à cause d’une violation du contrat. Le code 1 ne bloque pas — seul le code 2 le fait, et uniquement sur les événements qui gèrent le blocage.2 Les décisions JSON ne sont analysées qu’avec le code 0 — un script qui affiche {"decision": "block"} puis sort en 2 voit son JSON jeté. Et les matchers distinguent la casse — bash ne correspondra jamais à Bash. Confirmez l’enregistrement avec /hooks, puis testez en envoyant un JSON d’exemple dans le script par un pipe et en contrôlant echo $?.7

Sources

Vérifié par rapport à la documentation officielle le 8 août 2026. L’API des hooks a nettement évolué au fil des versions v2.1.x de Claude Code (nouveaux événements, nouveaux champs, nouvelle sémantique des matchers) : traitez donc les détails sensibles à la version comme valables à cette date précise.

À lire aussi sur ce site : la section hooks du guide Claude Code pour la vue d’ensemble du système, y compris les hooks de type prompt et agent, le tutoriel sur les hooks pour cinq réalisations de production avec leurs configurations complètes, Hooks pour le développement Apple pour les patterns iOS appliqués, et le démarrage rapide si vous n’avez pas encore installé Claude Code.


  1. Anthropic, « Hooks reference — Hook lifecycle and hook events ». code.claude.com/docs/en/hooks#hook-events 

  2. Anthropic, « Hooks reference — Hook input and output; exit code output; exit code 2 behavior per event ». code.claude.com/docs/en/hooks#exit-code-output 

  3. Anthropic, « Automate actions with hooks ». code.claude.com/docs/en/hooks-guide 

  4. Anthropic, « Hooks guide — Hooks and permission modes ». code.claude.com/docs/en/hooks-guide#hooks-and-permission-modes 

  5. Anthropic, « Hooks reference — JSON output and decision control ». code.claude.com/docs/en/hooks#json-output 

  6. Anthropic, « Hooks reference — Configuration: hook locations, matcher patterns, hook handler fields, le menu /hooks ». code.claude.com/docs/en/hooks#configuration 

  7. Anthropic, « Hooks guide — Limitations and troubleshooting ». code.claude.com/docs/en/hooks-guide#limitations-and-troubleshooting 

  8. Anthropic, « Hooks reference — Security considerations ». code.claude.com/docs/en/hooks#security-considerations 

  9. Anthropic, « Claude Code settings ». code.claude.com/docs/en/settings 

Articles connexes

Codex CLI vs Claude Code 2026 : architecture, tarifs et accès depuis la Chine

Codex CLI vs Claude Code en 2026 : bac à sable noyau, gouvernance par hooks, contexte des modèles, tarifs, accès cloud d…

42 min de lecture

Hooks Claude Code : pourquoi chacun de mes 95 hooks existe

J'ai construit 95 hooks pour Claude Code. Chacun existe parce que quelque chose a mal tourné. Voici leurs origines et l'…

12 min de lecture