Les hooks sont des commandes que Claude Code exécute à des points fixes dans une session : avant un appel d’outil, après, quand un tour se termine. C’est ainsi que vous formatez les fichiers, refusez les modifications à .env, et enregistrez les commandes shell sans vous souvenir de le demander. Les commandes slash qu’une personne tape vivent dans les commandes slash personnalisées. Cette page est le chemin automatique.
Où les hooks sont configurés
Une définition de hook a trois niveaux : l’événement, un groupe de matcher, et un ou plusieurs gestionnaires.
| Morceau | Exemple |
|---|---|
| Événement | PreToolUse, PostToolUse, Stop |
| Matcher | Nom d’outil ou type de notification. Omettez-le uniquement sur les événements qui ignorent les matchers |
| Gestionnaire | "type": "command" plus une commande shell |
| Fichier | Portée | Partager avec git |
|---|---|---|
~/.claude/settings.json |
Tous les projets sur cette machine | Non |
.claude/settings.json |
Ce dépôt | Oui |
.claude/settings.local.json |
Ce dépôt, vous uniquement | Gitignorée quand Claude Code y enregistre un paramètre |
Sur Windows, ~/.claude est %USERPROFILE%\.claude. La politique gérée et les plugins peuvent ajouter plus de hooks. Les cinq exemples ci-dessous appartiennent au fichier du projet pour que l’équipe les partage.
Les gestionnaires peuvent être une commande shell, un POST HTTP, une invite, un agent ou un outil MCP. Ces exemples utilisent type: command parce que le script est visible dans le dépôt. Les hooks de commande reçoivent le JSON d’événement sur stdin.
$CLAUDE_PROJECT_DIR est la racine du projet que Claude Code exporte pour les commandes hook. Citez-la. Les chemins avec des espaces se divisent sinon.
mkdir -p .claude/hooks
chmod +x .claude/hooks/*.sh
Sur macOS et Linux, le script doit être exécutable. Sur Windows natif, installez Git pour Windows. Si un hook rapporte que Git Bash n’a pas été trouvé, le hook peut bloquer l’appel d’outil jusqu’à ce que Bash soit disponible. Les étapes d’installation couvrent ce programme d’installation.
/hooks liste ce que la session a chargé. "disableAllHooks": true dans un fichier de paramètres désactive les hooks à ce niveau après l’application de la précédence. Une politique gérée peut toujours forcer les hooks.
Si vous modifiez le fichier de paramètres pendant qu’une session est ouverte, le watcher de fichier recharge généralement les hooks de lui-même.
Événements qui valent la peine d’être connus
La référence documente de nombreux événements. Ce sont ceux que les exemples utilisent, plus les voisins que vous verrez dans /hooks.
| Événement | Quand il se déclenche | Matcher |
|---|---|---|
PreToolUse |
Avant l’exécution d’un outil. Peut le bloquer | Nom de l’outil : Bash, Edit, Write |
PostToolUse |
Après la réussite d’un outil | Nom de l’outil |
Notification |
Quand Claude Code émet une notification | Type de notification, y compris permission_prompt, idle_prompt, auth_success, agent_needs_input, agent_completed |
Stop |
Quand Claude termine une réponse | Aucun. Il s’exécute toujours |
SubagentStop |
Quand un sous-agent se termine | Type d’agent |
UserPromptSubmit |
Quand une invite est soumise, avant que Claude Code ne la traite. Aussi sur les tours que Claude Code commence lui-même | Aucun. Il s’exécute toujours |
agent_completed ne se déclenche que pendant que la vue de l’agent est ouverte, donc c’est un mauvais signal « la tâche est terminée ». Utilisez Stop pour la fin d’un tour.
Aussi documenté, et laissé de côté des scripts ici : SessionStart, SessionEnd, Setup, PostToolUseFailure, PermissionRequest, PermissionDenied, SubagentStart, PreCompact, PostCompact, StopFailure, FileChanged, ConfigChange. Les matchers et les champs JSON pour ceux-ci vivent dans la référence des hooks. Un matcher est une expression régulière JavaScript testée contre le champ, et une correspondance n’importe où dans la valeur compte. Edit donc aussi matches NotebookEdit. Les motifs ci-dessous utilisent ^Edit$|^Write$ donc uniquement ces deux outils exécutent le script. La référence accepte | entre les alternatives sur les événements d’outils. Les exemples officiels montrent souvent le plus court Edit|Write. Utilisez la forme ancrée quand un nom d’outil plus long doit rester en dehors.
Stop et UserPromptSubmit ignorent les matchers. Un matcher sur ces événements ne les filtre pas.
Les champs courants sur stdin incluent session_id, cwd et hook_event_name. Les événements d’outils ajoutent tool_name, tool_input et tool_use_id. Pour Bash, la chaîne shell est tool_input.command. Pour Edit et Write, le chemin est tool_input.file_path.
Codes de sortie et JSON
| Code de sortie | Résultat |
|---|---|
| 0 | Succès. Un objet JSON sur stdout est appliqué. Les autres stdout sont du texte brut |
| 2 | Erreur bloquante sur les événements qui peuvent bloquer. stderr est la raison, à moins que JSON n’ait déjà porté une raison de décision |
| N’importe quel autre | Erreur non bloquante. L’action continue, et la transcription affiche une erreur de hook |
Sur PreToolUse, ce JSON refuse l’appel sans s’appuyer sur sortie 2 :
{
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "deny",
"permissionDecisionReason": "Protected path"
}
}
permissionDecision peut être deny, ask ou allow. La sortie 2 bloque toujours même si le JSON dit allow.
Stdout n’est JSON que quand c’est un objet unique et rien d’autre n’est imprimé. jq -n est un moyen fiable d’imprimer cet objet. Deux lignes de log avant le JSON transforment la sortie en texte brut.
Sur PostToolUse, l’outil a déjà terminé. La sortie 2 n’annule pas l’édition. Il nourrit stderr de retour à Claude. La sortie 0 stdout de PostToolUse va dans le journal de débogage, pas au modèle. UserPromptSubmit est différent : la sortie en texte brut est ajoutée au contexte que Claude peut lire, et la sortie 2 rejette l’invite.
Stop peut bloquer et demander à Claude de continuer. Après huit blocs d’affilée sans appel d’outil entre eux, Claude Code continue de toute façon. Les scripts dans cet atelier sortent 0 sur Stop pour qu’ils avisent uniquement.
Cinq hooks
Les scripts supposent que jq est installé (brew install jq ou le paquet de votre distro). Ce sont des wrappers originaux autour des champs stdin ci-dessus.
1. Format avec Prettier ou Black
.claude/hooks/format-file.sh
#!/bin/bash
# Format the file Claude just wrote, by extension.
input=$(cat)
path=$(printf '%s' "$input" | jq -r '.tool_input.file_path // empty')
[ -z "$path" ] && exit 0
[ -f "$path" ] || exit 0
case "$path" in
*.js|*.jsx|*.ts|*.tsx|*.css|*.md|*.json|*.yml|*.yaml)
npx prettier --write "$path" || exit 0
;;
*.py)
if command -v black >/dev/null 2>&1; then
black "$path" || exit 0
fi
;;
esac
exit 0
Les défaillances du formateur sortent 0 exprès. Une installation Prettier manquante ne doit pas ressembler à une édition bloquée. Si vous voulez que Claude voie un plantage du formateur, sortez 2 et imprimez l’erreur sur stderr à la place.
Enregistrez-le sur PostToolUse :
{
"hooks": {
"PostToolUse": [
{
"matcher": "^Edit$|^Write$",
"hooks": [
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/format-file.sh",
"timeout": 30
}
]
}
]
}
}
timeout est en secondes. Le défaut pour un hook de commande est 600, ce qui est trop long pour un formateur. Une commande Bash qui réécrit un fichier ne correspond pas à Edit ou Write. Un hook FileChanged est l’événement qui regarde un chemin peu importe quel outil l’a écrit.
Prettier et Black réécrivent tous deux le fichier sur disque après l’édition de Claude. La prochaine lecture voit la version formatée.
2. Bloquer les modifications aux fichiers protégés
.claude/hooks/protect-files.sh
#!/bin/bash
# Deny Edit/Write on secrets, lockfiles, and git internals.
input=$(cat)
path=$(printf '%s' "$input" | jq -r '.tool_input.file_path // empty')
path=${path//\\//}
case "$path" in
*.env|*.env.*|*/.env|*/.env.*)
echo "Blocked: $path is an env file. Edit a sample template instead." >&2
exit 2
;;
*/.git/*|*.git/*)
echo "Blocked: $path is inside .git." >&2
exit 2
;;
*package-lock.json|*pnpm-lock.yaml|*yarn.lock|*poetry.lock|*Cargo.lock)
echo "Blocked: $path is a lockfile. Update it through the package manager." >&2
exit 2
;;
esac
exit 0
{
"hooks": {
"PreToolUse": [
{
"matcher": "^Edit$|^Write$",
"hooks": [
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/protect-files.sh"
}
]
}
]
}
}
Claude reçoit le texte stderr et peut choisir un autre chemin. Cela n’arrête pas une commande Bash telle que rm .env ou une redirection. Ajoutez un deuxième groupe PreToolUse avec matcher ^Bash$ si les écritures shell à ces chemins importent. Pour un refus dur qui sort toujours 0, imprimez le JSON permissionDecision de la section du code de sortie et sortez 0.
3. Enregistrer les commandes shell
.claude/hooks/log-bash.sh
#!/bin/bash
# Append each Bash command to a local log. Never block.
input=$(cat)
log="${CLAUDE_PROJECT_DIR:-.}/.claude/hooks/bash-log.jsonl"
printf '%s\n' "$input" | jq -c '{t: (now | todate), cmd: .tool_input.command, cwd: .cwd}' >> "$log"
exit 0
Ajoutez .claude/hooks/bash-log.jsonl à .gitignore. Le journal contiendra tout ce que Claude a mis dans la chaîne de commande, y compris les secrets si une commande les incorpore.
{
"hooks": {
"PreToolUse": [
{
"matcher": "^Bash$",
"hooks": [
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/log-bash.sh"
}
]
}
]
}
}
if peut affiner cela davantage sur les événements d’outils, en utilisant la syntaxe des règles de permission telle que Bash(git *). Sans if, chaque appel Bash ajoute une ligne. Un if qui ne correspond pas ignore entièrement le processus.
4. Notifier quand le tour se termine
Stop n’a pas de matcher. Gardez la commande courte. Le timeout par défaut est long, et un notificateur bloqué retient la fin du tour.
macOS, après que Script Editor soit autorisé à notifier :
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "osascript -e 'display notification \"Turn finished\" with title \"Claude Code\"'",
"timeout": 5
}
]
}
]
}
}
Linux, quand un daemon de notification est en cours d’exécution :
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "notify-send 'Claude Code' 'Turn finished'",
"timeout": 5
}
]
}
]
}
}
Windows PowerShell ouvre une boîte de dialogue, qui peut atterrir derrière le terminal :
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "powershell.exe -NoProfile -Command \"[System.Reflection.Assembly]::LoadWithPartialName('System.Windows.Forms') | Out-Null; [System.Windows.Forms.MessageBox]::Show('Turn finished','Claude Code') | Out-Null\"",
"timeout": 15
}
]
}
]
}
}
notify-send ne fait rien d’utile sur SSH ou dans un conteneur. Testez la commande dans un terminal normal avant de la mettre dans les paramètres. Un hook Notification avec matcher permission_prompt est le cas séparé « Claude vous attend », ce que vous voulez pendant une longue pause, pas à la fin.
SubagentStop est l’événement de correspondance pour un sous-agent qui se termine. Le matcher est le name de l’agent, par exemple code-reviewer. Un hook Stop dans le propre front matter de cet agent est livré en tant que SubagentStop.
5. Exécuter les tests après une édition
Exécuter une suite complète après chaque frappe est bruyant. Ce script s’exécute uniquement pour les fichiers de test Python et JavaScript, et uniquement quand un point d’entrée connu existe.
.claude/hooks/test-after-edit.sh
#!/bin/bash
# Re-run a narrow test file after Claude edits it.
input=$(cat)
path=$(printf '%s' "$input" | jq -r '.tool_input.file_path // empty')
case "$path" in
*test_*.py|*tests/*.py)
pytest "$path" >&2 || exit 2
;;
*.test.js|*.test.ts|*.spec.js|*.spec.ts)
# Extra args are appended to the npm test script. Point this at the
# runner binary if that script ignores a file path.
npm test -- "$path" >&2 || exit 2
;;
esac
exit 0
Pytest écrit sur stderr ici pour qu’un échec soit la raison de blocage que Claude lit. La sortie 2 sur PostToolUse n’annule pas le fichier. Elle indique à Claude que l’édition a atterri et que la commande de test a échoué.
{
"hooks": {
"PostToolUse": [
{
"matcher": "^Edit$|^Write$",
"hooks": [
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/test-after-edit.sh",
"timeout": 120
}
]
}
]
}
}
Remplacez npm test par le runner que votre dépôt documente déjà. Une commande slash nommée /test est le meilleur ajustement quand une personne doit décider d’exécuter la suite. Le hook est pour les éditions que vous ne voulez pas fusionner sans une vérification rouge/vert.
Un fichier de paramètres
Fusionnez les groupes. Deux groupes PostToolUse sont normaux. Les gestionnaires dans un groupe s’exécutent quand le matcher correspond.
{
"hooks": {
"PreToolUse": [
{
"matcher": "^Edit$|^Write$",
"hooks": [
{ "type": "command", "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/protect-files.sh" }
]
},
{
"matcher": "^Bash$",
"hooks": [
{ "type": "command", "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/log-bash.sh" }
]
}
],
"PostToolUse": [
{
"matcher": "^Edit$|^Write$",
"hooks": [
{ "type": "command", "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/format-file.sh", "timeout": 30 },
{ "type": "command", "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/test-after-edit.sh", "timeout": 120 }
]
}
],
"Stop": [
{
"hooks": [
{ "type": "command", "command": "notify-send 'Claude Code' 'Turn finished'", "timeout": 5 }
]
}
]
}
}
Échangez la commande Stop pour la ligne macOS ou Windows si vous n’êtes pas sur Linux. Demandez à Claude d’éditer un fichier sous src/ et confirmez que le journal a grandi et que le formateur a fonctionné. Demandez-lui d’éditer .env et confirmez que l’appel de l’outil est refusé.
Les hooks sont de l’automatisation locale. Ce ne sont pas un substitut à CI, et ce ne sont pas un serveur MCP. Un serveur MCP ajoute des outils que Claude peut appeler. Un hook enveloppe les outils que Claude a déjà.
FAQ
Où vont les hooks Claude Code ?
Ajoutez un objet hooks à .claude/settings.json pour partager les hooks avec le dépôt, ou à ~/.claude/settings.json pour les exécuter dans chaque projet. .claude/settings.local.json contient les remplacements de projet personnel et est gitignorée quand Claude Code y enregistre un paramètre. Exécutez /hooks dans une session pour voir la liste fusionnée. Définissez disableAllHooks à true pour désactiver les hooks à ce niveau des paramètres.
Quelle est la différence entre le code de sortie 0 et le code de sortie 2 ?
La sortie 0 signifie le succès. Si stdout est un objet JSON, Claude Code applique ces champs. La sortie 2 bloque les événements qui peuvent bloquer, et stderr est la raison que Claude voit. Tout autre code de sortie est une erreur non bloquante : l’appel de l’outil procède toujours, sauf si ce hook est configuré pour que l’échec bloque. Sur PreToolUse, un permissionDecision JSON valide peut refuser l’appel même si le processus sort 0.
Quel hook s’exécute avant que Claude n’édite un fichier ?
PreToolUse, avec un matcher pour les outils Edit et Write. Le script lit JSON sur stdin. Le chemin cible est tool_input.file_path. Sortez 2 pour annuler l’édition et imprimez la raison sur stderr. PostToolUse s’exécute uniquement après que l’outil a réussi, donc c’est le bon endroit pour formater, pas pour empêcher l’écriture.
Un hook peut-il exécuter Prettier ou Black ?
Oui. Un hook de commande PostToolUse peut lire tool_input.file_path et exécuter Prettier sur les fichiers JavaScript et TypeScript ou Black sur les fichiers Python. La sortie du hook n’est pas affichée à Claude sur sortie 0. Si le formateur échoue et que vous sortez 2, Claude voit stderr même si l’édition a déjà atterri.
Stop et UserPromptSubmit acceptent-ils un matcher ?
Non. Stop et UserPromptSubmit se déclenchent à chaque occurrence. Les matchers s’appliquent aux événements d’outils tels que PreToolUse et PostToolUse, où le motif est mis en correspondance avec le nom de l’outil, et à Notification, où il est mis en correspondance avec le type de notification. SubagentStop correspond au type d’agent.
Comment ce guide a été vérifié
Rédigé à la main à partir de la documentation officielle, chaque commande revérifiée, puis relu une seconde fois avant publication. Dernière relecture le .
Sources officielles consultées pour cet article

