Les hooks de Claude Code expliqués : la couche déterministe autour de votre agent
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,UserPromptExpansionetSessionStart, 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
additionalContextest injecté en texte brut. PostToolUsene peut rien annuler. L’outil a déjà tourné. La prévention se joue dansPreToolUse.Stopse 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éclenchentStopFailureà la place). La logique du verrou doit tolérer les arrêts en pleine tâche.PermissionRequestne se déclenche pas dans une exécution headless (-p) simple. Il se déclenche bien sous-pquand un callbackcanUseToolde 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, prenezPreToolUse.PreToolUsene 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 surRead.1- Un
updatedInputparallè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 pourMessageDisplay) ; 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), avecclaude --debug-file /tmp/claude.logou avec/debugen 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.
-
Anthropic, « Hooks reference — Hook lifecycle and hook events ». code.claude.com/docs/en/hooks#hook-events ↩↩↩↩↩↩
-
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 ↩↩↩↩↩↩↩↩
-
Anthropic, « Automate actions with hooks ». code.claude.com/docs/en/hooks-guide ↩↩↩
-
Anthropic, « Hooks guide — Hooks and permission modes ». code.claude.com/docs/en/hooks-guide#hooks-and-permission-modes ↩↩
-
Anthropic, « Hooks reference — JSON output and decision control ». code.claude.com/docs/en/hooks#json-output ↩↩↩↩↩↩↩
-
Anthropic, « Hooks reference — Configuration: hook locations, matcher patterns, hook handler fields, le menu /hooks ». code.claude.com/docs/en/hooks#configuration ↩↩↩↩↩↩↩
-
Anthropic, « Hooks guide — Limitations and troubleshooting ». code.claude.com/docs/en/hooks-guide#limitations-and-troubleshooting ↩↩↩↩↩
-
Anthropic, « Hooks reference — Security considerations ». code.claude.com/docs/en/hooks#security-considerations ↩
-
Anthropic, « Claude Code settings ». code.claude.com/docs/en/settings ↩