Blog

Hooks Claude Code : configuration et sept exemples

Markdown

7 hooks prêts à l’emploi pour .claude/settings.json, à copier-coller : protection de .env, prettier, force push vers main, tirets cadratins et serveur de développement. 4 types de gestionnaires et 31 événements.

Lire l’article complet

Dans Claude Code, les hooks sont des commandes shell, des appels HTTP ou de courtes vérifications par LLM qui s’exécutent automatiquement avant ou après une action de Claude. Ils ajoutent des contrôles à certains événements d’outils, formatent après modification et injectent du contexte en début de session. Un PreToolUse correspondant peut rejeter cet appel ; ces regex sont des garde-fous pratiques, pas une limite de sécurité couvrant tous les chemins de fichiers ou Git. Voici une présentation des 4 types de gestionnaires, des 31 événements du cycle de vie, un test pour déterminer « quand une règle doit être un hook », 7 hooks prêts à copier dans .claude/settings.json et 3 pièges que j’ai moi-même rencontrés.

Documentation Ailoot : API, clés et configuration.

Que sont les hooks dans Claude Code ?

À retenir

Un hook est une commande shell, un appel HTTP, un court prompt LLM ou un sous-agent que Claude Code exécute en réponse à un événement précis du cycle de vie. Contrairement aux instructions de CLAUDE.md, que le modèle peut ignorer ou oublier, un hook s’exécute toujours de la même manière : c’est du code, pas une suggestion.

Pendant six mois, j’ai gardé la règle « ne jamais modifier .env directement » dans CLAUDE.md. Claude l’enfreignait une fois par mois environ. Pas intentionnellement ; il l’oubliait simplement à la fin d’une longue session. Une fois, un jeton supplémentaire s’est retrouvé dans .env ; une autre fois, une variable portant un nom en double a écrasé celle qui était utilisée.

J’ai déplacé cette règle dans un hook, ce qui la rend impossible à oublier. Quand un outil de modification directe correspondant fournit un chemin .env, le hook rejette cet appel. Redirections Bash, scripts, MCP et autres accès exigent des contrôles distincts ; cet exemple ne rend pas le fichier immuable.

Les hooks sont des commandes shell, des points de terminaison HTTP ou des prompts LLM que vous définissez et qui s’exécutent automatiquement à des moments précis du cycle de vie de Claude Code.

— Anthropic, https://code.claude.com/docs/en/hooks

Autrement dit, un hook est une commande que Claude Code exécute lors d’un événement précis. Chaque modification de fichier, lancement de Bash, début de session ou fin d’exécution d’un sous-agent est un événement auquel vous pouvez associer un hook.

Pourquoi c’est utile pour les vibe codeurs :

  • Protéger les fichiers contenant des secrets contre les modifications accidentelles.
  • Exécuter automatiquement prettier/eslint après chaque génération de code.
  • Bloquer git push --force vers main sans autorisation explicite.
  • Injecter du contexte récent au début de chaque session.
  • Consigner les actions de Claude à des fins d’audit.

CLAUDE.md gère le contexte. Les hooks gèrent les actions. Ce sont deux choses différentes, et les confondre est la principale erreur des débutants.

Quelle est la différence entre les hooks, CLAUDE.md et les skills ?

À retenir

CLAUDE.md est un texte que le modèle lit au début d’une session et qu’il essaie de suivre. Un hook est une commande shell que le processus Claude Code exécute en réponse à un événement, indépendamment du modèle. Un skill est un ensemble nommé d’instructions que Claude peut appeler avec l’outil Skill, comme une fonction. Ces trois éléments répondent à des types de tâches différents.

La documentation d’Anthropic explique explicitement la différence entre CLAUDE.md et les hooks :

Le contenu de CLAUDE.md est transmis sous forme de message utilisateur après le prompt système, et non dans le prompt système lui-même. Claude le lit et essaie de le suivre, mais son respect strict n’est pas garanti.

— Anthropic, documentation sur la mémoire, https://code.claude.com/docs/en/memory

Autrement dit, le contenu de CLAUDE.md parvient à Claude sous la forme du premier message de l’utilisateur, et non d’une instruction système. Le modèle le lit et essaie de le suivre. Mais rien ne le garantit, surtout vers la fin d’une longue session, quand le contexte s’allonge et que les instructions plus anciennes sont écartées.

Un hook fonctionne autrement. Il se trouve dans settings.json, ne dépend pas du modèle et est exécuté par le processus Claude Code lors d’un événement précis : PreToolUse, PostToolUse, SessionStart et d’autres. La commande s’exécute avant que Claude reçoive le résultat et peut bloquer concrètement une action (exit 2) ou modifier un fichier après sa modification (exit 0).

Comparaison :

FonctionnalitéCLAUDE.mdHookSkill
EmplacementCLAUDE.md à la racine du projet.claude/settings.jsonDossier .claude/skills/
Qui l’exécute ?Le modèle le lit dans un message utilisateurLe processus Claude Code lors d’un événementLe modèle via l’outil Skill
Exécution garantie ?Non (au mieux)Le hook configuré correspondant exécute du code ; échecs et sorties dépendent de l’événementVia un appel au modèle (avec une autorisation, mais pas au niveau du shell)
UsageContexte du projet, ton, style, routageRestrictions, formatage, vérifications portant sur certains fichiersProcédures complexes en plusieurs étapes avec paramètres
Quand s’exécute-t-il ?À chaque message du modèle dans une sessionLors d’un événement du cycle de vieQuand le modèle l’appelle
Peut-il bloquer une action ?NonOui (exit 2)Non

Le modèle complet de CLAUDE.md et 6 règles pour garder ce fichier vivant figurent dans le guide Comment configurer CLAUDE.md en 2026 : un modèle prêt à l’emploi et 6 règles. Découvrez les skills comme capacités nommées dans Claude Code Skills : comment créer votre propre bibliothèque.

Un hook se situe au niveau le plus bas : pas de modèle, pas d’interprétation, juste le shell.

Les hooks sont l’un des outils d’ingénierie du contexte, aux côtés de Plan Mode et de /compact.

4 types de hooks : Command, HTTP, Prompt, Agent — lequel choisir ?

À retenir

Claude Code propose 4 types de gestionnaires. Command est un script shell, le type le plus courant. HTTP envoie une requête POST à votre URL, pour les équipes et les intégrations. Prompt effectue un court appel à Haiku pour une vérification sémantique. Agent crée un sous-agent ayant accès aux outils pour une validation approfondie. Les débutants ont besoin de Command et Prompt ; ils couvrent 90% des tâches.

TypeFonctionQuand le choisirComplexité
CommandExécute une commande shell, reçoit du JSON sur stdin et répond avec un code de sortie et stdout90% des cas : vérifier des chemins, formater, rechercher dans le contenu avec grep, lsof, opérations Gitfaible
HTTPEnvoie une requête POST avec un corps JSON à votre URL et reçoit du JSON en réponseQuand une règle concerne une équipe ou une CI : validation centralisée, tableau de bord d’état partagé, audit à distancemoyenne (nécessite un serveur)
PromptEnvoie un court prompt à Haiku (par défaut) et obtient une réponse oui/non pour une évaluation sémantiqueQuand grep ne suffit pas : « évalue si ce texte a un ton d’entreprise »faible
AgentCrée un sous-agent ayant accès à Read/Grep/Glob pour des vérifications en plusieurs étapesValidation approfondie : « trouve tous les fichiers modifiés et vérifie que les tests correspondants existent »élevée

L’erreur la plus courante consiste à utiliser Command pour une tâche qui relève de Prompt. Par exemple, « ne pas utiliser de tirets cadratins dans les fichiers .md » peut se vérifier avec grep, donc Command est idéal. Mais « ne pas écrire sur un ton d’entreprise » relève de la sémantique et nécessite Prompt.

Si vous débutez, utilisez Command. Les 7 hooks prêts à l’emploi ci-dessous sont tous de type Command. Vous pourrez passer à HTTP/Prompt/Agent ensuite, quand vous serez prêt.

Où se trouve settings.json et quels sont les 4 niveaux de configuration ?

À retenir

Claude Code recherche la configuration à 4 endroits : entreprise (déployée via MDM), utilisateur (~/.claude/settings.json), projet (.claude/settings.json à la racine du dépôt) et local (.claude/settings.local.json, absent de Git). Les 4 fichiers sont fusionnés au début de la session, le niveau inférieur étant prioritaire.

NiveauCheminPortéeDans Git ?
Entreprise/Library/Application Support/ClaudeCode/settings.json (Mac), /etc/claude-code/settings.json (Linux)À l’échelle de l’équipe via MDMnon
Utilisateur~/.claude/settings.jsonTous les projets d’un utilisateurnon
Projet.claude/settings.json à la racine du dépôtCe projet, pour toute l’équipeoui
Local.claude/settings.local.jsonCe projet uniquement, pour cet utilisateur uniquementnon (dans .gitignore)

Je garde les 7 hooks décrits ci-dessous dans le fichier .claude/settings.json du projet, afin qu’ils soient inclus automatiquement lors du clonage du dépôt et que Claude suive les mêmes règles dans une nouvelle fenêtre. Les hooks expérimentaux personnels vont dans .claude/settings.local.json pour ne pas gêner l’équipe.

Les 4 fichiers sont fusionnés au début de la session. En cas de conflit entre les règles, le niveau inférieur est prioritaire (local sur projet, projet sur utilisateur).

Le moyen le plus rapide de créer votre premier hook consiste à utiliser le menu interactif /hooks directement dans Claude Code. Choisissez un événement dans la liste, sélectionnez un matcher (par exemple, Bash ou Edit), puis collez une commande. Claude Code écrit un JSON valide dans settings.json.

31 événements du cycle de vie : lesquels sont utiles aux débutants ?

À retenir

Claude Code compte actuellement 31 événements du cycle de vie, répartis en 3 fréquences : une fois par session (SessionStart/SessionEnd), une fois par tour (UserPromptSubmit/Stop) et à chaque appel d’outil (PreToolUse/PostToolUse). Les débutants ont besoin de 4 des 31 événements : PreToolUse pour les restrictions, PostToolUse pour les modifications automatiques, SessionStart pour le contexte et Stop pour les vérifications finales.

Les événements se produisent selon trois fréquences : une fois par session (SessionStart, SessionEnd), une fois par tour (UserPromptSubmit, Stop, StopFailure) et à chaque appel d’outil dans la boucle agentique (PreToolUse, PostToolUse).

— Anthropic, référence des hooks, https://code.claude.com/docs/en/hooks

Il est important de comprendre ces trois fréquences pour éviter toute confusion :

  1. Une fois par session - SessionStart, SessionEnd, Setup. Ces événements se déclenchent une fois à l’ouverture et à la fermeture de Claude Code. Utilisez-les pour initialiser le contexte et nettoyer les fichiers temporaires.
  2. Une fois par tour - UserPromptSubmit, UserPromptExpansion, Stop, StopFailure. Ils se déclenchent à chacun de vos messages et chaque fois que le modèle s’arrête. Utilisez-les pour consigner la conversation et effectuer les vérifications finales du résultat.
  3. À chaque appel d’outil - PreToolUse, PostToolUse, PostToolUseFailure, PermissionRequest, PermissionDenied, PostToolBatch, SubagentStart, SubagentStop. Ils se déclenchent à chaque modification de fichier, lancement de Bash, lecture ou recherche. C’est la couche la plus sollicitée, où se trouvent les restrictions, le formatage et les validations.

La liste complète des 31 événements comprend des événements asynchrones/réactifs : TeammateIdle, PreCompact, PostCompact, Elicitation, ElicitationResult, InstructionsLoaded, ConfigChange, CwdChanged, FileChanged, WorktreeCreate, WorktreeRemove, Notification. Ils sont réservés aux scénarios avancés ; laissez-les de côté pour le moment.

Test en cinq questions : quand une règle doit-elle devenir un hook et quand doit-elle rester dans CLAUDE.md ?

À retenir

Si une règle porte sur une action (et non sur le contexte), concerne la sécurité/l’argent/les données, peut être vérifiée avec grep/regex, serait difficile à annuler en cas d’infraction et que Claude l’a déjà enfreinte au moins une fois, elle doit devenir un hook. Si moins de trois de ces cinq réponses sont « oui », elle reste dans CLAUDE.md. J’ai appliqué ce test à mes 80 lignes et déplacé 5 règles dans des hooks.

Pour moi, c’est une loi, pas une suggestion. J’ai résumé le choix entre CLAUDE.md et un hook en un simple test de cinq questions. Si au moins trois réponses sont « oui », la règle devient un hook. Avec moins de trois réponses positives, elle reste dans le texte.

Le test « quand une règle doit-elle devenir un hook ? » :

  1. Cette règle porte-t-elle sur une action plutôt que sur le contexte ? (« ne pas exécuter rm -rf » concerne une action ; « j’ai un projet TypeScript » relève du contexte.)
  2. Porte-t-elle sur la sécurité, l’argent ou les données ? (« ne pas toucher à .env », « ne pas publier de clés ».)
  3. Peut-on la vérifier avec grep, un regex ou le code de sortie d’une commande shell ? (« après la modification de .md, vérifier qu’il n’y a pas de tiret cadratin. »)
  4. Si la règle est enfreinte, les conséquences seraient-elles difficiles à annuler ? (« forcer un push vers main » est difficile à annuler ; « oublier un point final » est facile.)
  5. Avez-vous déjà constaté que Claude oubliait cette règle au moins une fois ?

Exemple : « Ne pas utiliser de tirets cadratins ; utiliser uniquement des traits d’union. » Action ? Oui. Sécurité ? Non. Vérifiable avec grep ? Oui. Difficile à annuler ? Non. Déjà enfreinte ? Oui. 3 sur 5 : mettez-la dans un hook.

Contre-exemple : « J’ai des vibe codeurs, pas des développeurs ; écris sans argot. » Action ? Plutôt du contexte. Sécurité ? Non. Vérifiable avec un regex ? Trop difficile. Difficile à annuler ? Non. Déjà enfreinte ? Oui. 1 sur 5 : gardez-la dans CLAUDE.md.

Cinq de mes règles ont réussi le test. Elles sont passées dans des hooks. Les 75% restants sont restés dans le fichier texte.

7 hooks prêts à copier

À retenir

Voici 7 hooks prêts à l’emploi pour .claude/settings.json : bloquer les modifications de .env, lancer prettier automatiquement, empêcher les force push vers main, détecter les tirets cadratins, libérer un port avant npm run dev, effectuer un commit automatique après des tests réussis et injecter du contexte récent au début d’une session. Copiez chaque hook dans le fichier, redémarrez Claude Code et c’est opérationnel.

Les 7 hooks sont réunis dans un seul fichier .claude/settings.json à la racine du projet. Chaque entrée contient un type d’événement, un filtre d’outil (matcher) et une commande shell. Le code 2 rejette un appel PreToolUse correspondant avant exécution. Pour PostToolUse, l’action a déjà eu lieu : le hook donne un retour sans pouvoir l’annuler.

Pour la plupart des événements de hook, seul le code de sortie 2 bloque une action. Claude Code traite le code de sortie 1 comme une erreur non bloquante et poursuit l’action, même si 1 signifie généralement un échec sous Unix.

— Anthropic, référence des hooks, https://code.claude.com/docs/en/hooks

exit 0 signifie réussite. exit 1 signifie erreur, mais Claude continue (c’est le premier anti-pattern ci-dessous). exit 2 dépend de l’événement : il peut bloquer PreToolUse, tandis que PostToolUse rapporte seulement une action déjà réalisée.

Hook 1. Contrôler les modifications directes de .env (PreToolUse, exit 2)

Bloque les tentatives de modification directe de tout fichier correspondant à .env, .env.local, .env.production, etc.

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Edit|Write|MultiEdit",
        "hooks": [
          {
            "type": "command",
            "command": "FILE=$(jq -r '.tool_input.file_path // empty'); [ -z \"$FILE\" ] && exit 0; echo \"$FILE\" | grep -qE '(^|/)\\.env(\\.|$)' && { echo 'Do not edit `.env` directly. Tell me which variable to add, and I’ll open the file myself.' >&2; exit 2; } || exit 0"
          }
        ]
      }
    ]
  }
}

Ce qu’il fait : Avant chaque Edit/Write/MultiEdit, il récupère le chemin du fichier, vérifie avec grep s’il correspond au motif .env et, si c’est le cas, renvoie le code de sortie 2 accompagné d’un message. Claude voit stderr et me dit : « J’ai essayé de modifier .env, mais le hook m’a bloqué ; dites-moi quelle variable ajouter. »

Hook 2. Lancer prettier automatiquement après chaque modification de code (PostToolUse, exit 0)

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write|MultiEdit",
        "hooks": [
          {
            "type": "command",
            "command": "FILE=$(jq -r '.tool_input.file_path // empty'); [ -z \"$FILE\" ] && exit 0; echo \"$FILE\" | grep -qE '\\.(ts|tsx|js|jsx|json)$' && npx prettier --write \"$FILE\" 2>/dev/null & exit 0"
          }
        ]
      }
    ]
  }
}

Ce qu’il fait : Après chaque Edit/Write, il récupère le chemin et, si l’extension est .ts/.tsx/.js/.jsx/.json, lance prettier en arrière-plan avec &. Il renvoie toujours le code de sortie 0, car il s’agit de formatage, pas d’une restriction. Si prettier échoue, ce n’est pas grave ; le fichier reste tel quel.

La différence avec le hook 1 est fondamentale : PreToolUse + exit 2 constitue un blocage strict avant l’action. PostToolUse + exit 0 apporte une modification légère après l’action. Ce sont deux modes différents, adaptés à des tâches différentes.

Hook 3. Bloquer git push --force vers la branche main (PreToolUse, exit 2)

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "CMD=$(jq -r '.tool_input.command // empty'); [ -z \"$CMD\" ] && exit 0; echo \"$CMD\" | grep -qE 'git[[:space:]]+push' && echo \"$CMD\" | grep -qE '(\\-\\-force|\\-\\-force-with-lease|[[:space:]]-f([[:space:]]|$))' && echo \"$CMD\" | grep -qE '([[:space:]/:])main([[:space:]/:]|$)' && { echo 'Force push to main is forbidden. Create a new branch or ask me to do it manually.' >&2; exit 2; } || exit 0"
          }
        ]
      }
    ]
  }
}

Ce qu’il fait : À chaque lancement de Bash, il vérifie si la commande contient push, --force/--force-with-lease/-f et mentionne main. Si les trois conditions sont réunies, il bloque l’action avec un message clair.

Avant, j’avais cette règle en majuscules dans CLAUDE.md. Claude l’enfreignait encore environ toutes les deux semaines quand je lui demandais de « forcer le push pour annuler mon mauvais commit ». Le hook reconnaît les commandes littérales montrées, mais alias, scripts, branche implicite ou autre client Git peuvent contourner ce contrôle de chaîne. Protégez main sur le distant et limitez les identifiants de l’agent.

Hook 4. Bloquer les tirets cadratins dans les fichiers texte (PostToolUse, exit 2)

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write|MultiEdit",
        "hooks": [
          {
            "type": "command",
            "command": "FILE=$(jq -r '.tool_input.file_path // empty'); [ -z \"$FILE\" ] && exit 0; echo \"$FILE\" | grep -qE '\\.(md|mdx|tsx?)$' && grep -q $'\\xe2\\x80\\x94' \"$FILE\" && { echo \"File $FILE contains an em dash (U+2014). Replace it with a regular hyphen (U+002D).\" >&2; exit 2; } || exit 0"
          }
        ]
      }
    ]
  }
}

Ce qu’il fait : PostToolUse avec exit 2 signale à Claude un échec après modification, sans annuler ni empêcher l’écriture. Si Claude modifie un fichier et y laisse un tiret cadratin, le hook échoue, Claude reçoit un retour et réessaie. Après 2–3 sessions, Claude cesse complètement d’insérer ce caractère.

Notez que grep recherche spécifiquement le caractère du tiret cadratin (U+2014) à l’aide de $'\xe2\x80\x94', la notation Bash qui représente ses octets UTF-8. Si vous écrivez simplement grep -q '-', la commande détectera aussi les traits d’union ordinaires et s’exécutera sur chaque fichier. Un caractère invisible peut faire échouer toute la logique.

Hook 5. Libérer le port 3000 avant npm run dev (PreToolUse, exit 0)

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "CMD=$(jq -r '.tool_input.command // empty'); [ -z \"$CMD\" ] && exit 0; echo \"$CMD\" | grep -qE '(npm|pnpm|yarn)[[:space:]]+run[[:space:]]+dev' && { lsof -ti:3000 | xargs -r kill -9 2>/dev/null; echo 'Port 3000 freed before launch' >&2; }; exit 0"
          }
        ]
      }
    ]
  }
}

Ce qu’il fait : Avant chaque npm run dev (ou pnpm/yarn), il tue le processus qui occupe le port 3000. Il renvoie toujours le code de sortie 0, car il s’agit de préparation, pas d’une restriction. Cela évite une catégorie de pièges où Claude voit « port in use », pense qu’il y a un problème de code et commence à corriger des choses alors que l’ancien serveur tournait simplement encore.

Hook 6. Créer automatiquement un commit après des tests réussis (PostToolUse, exit 0)

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "INPUT=$(cat); CMD=$(printf '%s' \"$INPUT\" | jq -r '.tool_input.command // empty') || exit 1; EXIT=$(printf '%s' \"$INPUT\" | jq -r '.tool_response.exit_code // empty') || exit 1; case \"$CMD\" in 'npm test'|'pnpm test'|'yarn test') ;; *) exit 0 ;; esac; [ \"$EXIT\" = \"0\" ] || exit 0; git diff --cached --quiet || { echo 'Existing staged changes: review manually' >&2; exit 0; }; git add -- src/orders.ts tests/orders.test.ts || exit 1; git diff --cached --quiet && exit 0; git commit -m 'tests: checkpoint after successful run'"
          }
        ]
      }
    ]
  }
}

Ce qu’il fait : Il lit stdin JSON une seule fois et accepte seulement les commandes de test simples exactes affichées. Il poursuit si tool_response.exit_code vaut explicitement zéro ; si ce champ manque dans votre version, il saute le commit sans supposer la réussite. Remplacez src/orders.ts et tests/orders.test.ts par les fichiers exacts attribués à la tâche. Des changements déjà indexés arrêtent la recette pour exclure le travail sans rapport. Un ajout raté arrête, un diff vide ne crée rien et les hooks ordinaires restent actifs. Testez hors ligne succès, échec et résultat manquant avant activation.

Anti-pattern : ajouter git commit --no-verify à chaque Edit. Gardez les contrôles de commit. Si l’automatisation récursive les déclenche, réduisez le déclencheur ou repensez le contrôle sans contourner la validation par défaut. Examinez diff indexé et propriété des fichiers avant activation.

Hook 7. Injecter du contexte récent au début d’une session (SessionStart avec additionalContext)

{
  "hooks": {
    "SessionStart": [
      {
        "matcher": "*",
        "hooks": [
          {
            "type": "command",
            "command": "BRANCH=$(git symbolic-ref --short HEAD 2>/dev/null || echo 'no-branch'); STATUS=$(git status --short 2>/dev/null | head -10); echo \"{\\\"additionalContext\\\":\\\"Current branch: $BRANCH. Uncommitted changes:\\n$STATUS\\\"}\""
          }
        ]
      }
    ]
  }
}

Ce qu’il fait : Au début de la session, il récupère la branche Git actuelle et la liste des modifications non validées, puis renvoie du JSON contenant le champ additionalContext. Claude Code injecte ce texte dans le premier message système ; Claude sait ainsi dès le départ où il se trouve et ce qui reste de la session précédente.

Utilisez additionalContext pour les informations que Claude doit connaître sur l’état actuel de l’environnement ou une opération tout juste terminée : état de l’environnement (branche actuelle, cible de déploiement, indicateurs de fonctionnalité actifs), règles de projet conditionnelles (quelle commande de test s’applique au fichier qui vient d’être modifié), données externes (tickets ouverts qui vous sont attribués, résultats récents de CI).

— Anthropic, référence des hooks, https://code.claude.com/docs/en/hooks

Extensions possibles de ce hook : récupérer le dernier commit et sa description, l’état de GitHub Actions, le sprint en cours dans Linear/Jira et les tickets ouverts du dépôt actuel.

3 pièges liés aux hooks qui surprennent une personne sur deux

À retenir

Premier piège : un hook qui renvoie exit 1 laisse l’action passer sans bruit, et Claude ignore l’erreur. Deuxième piège : les opérations lourdes dans un hook sont interrompues après un délai maximal de 60 secondes. Troisième piège : un hook ne voit que les variables d’environnement de ~/.zshrc, pas le .env du projet. Chaque problème se règle en une ligne, mais si vous ne connaissez pas ces pièges, vous pourriez croire que « les hooks ne fonctionnent pas ».

Piège 1. Un hook avec exit 1 laisse passer l’action sans bruit

C’est le plus dangereux. Pendant une semaine, j’ai cru que mon protecteur de .env fonctionnait, mais j’avais en fait utilisé jq -r '.tool_input.file_path' dans le hook sans vérifier si la valeur était nulle. Quand tool_input était vide, jq renvoyait le code 1. Claude interprétait exit 1 comme « erreur du hook, continuer », et l’action était exécutée. Je ne comprenais pas pourquoi .env était toujours modifié. Je n’ai découvert la cause qu’en lançant manuellement le hook dans un terminal avec une entrée vide.

La solution tient en une règle : commencez chaque hook par vérifier si la valeur est nulle et terminez-le par || exit 0. Toute sortie inattendue entraîne alors une sortie « sûre », au lieu de laisser l’action passer sans bruit.

# Safe start and end template:
FILE=$(jq -r '.tool_input.file_path // empty')
[ -z "$FILE" ] && exit 0
# ... your logic ...
exit 0   # or || exit 0 on one line

Piège 2. Les hooks lents sont interrompus par un délai maximal

J’avais un hook qui exécutait eslint --fix sur tout le fichier après Edit. Le traitement des fichiers volumineux (1000+ lignes) prenait 8–10 secondes. Le délai maximal par défaut des hooks de Claude Code est de 60 secondes, donc je pensais rester dans la limite. Mais quand plusieurs Edit se produisent en une minute, les hooks s’accumulent et font la queue, et certains sont tout simplement ignorés. Je ne m’en suis rendu compte que lorsque le journal a affiché « hook timed out ».

Solution : si un hook nécessite un traitement lourd, exécutez-le en arrière-plan. Lancez eslint --fix "$FILE" & sans attendre le résultat. Le hook renvoie alors exit 0 en quelques millisecondes, tandis que le travail réel s’effectue en arrière-plan.

# Heavy operation in background:
npx eslint --fix "$FILE" &
exit 0

Piège 3. Un hook ne peut pas accéder aux variables d’environnement du projet

Je pensais que process.env serait disponible dans un hook comme dans une application Node.js. Ce n’est pas le cas. Le hook s’exécute dans un shell lancé par le processus parent Claude Code et ne voit que ce qui se trouve dans ~/.zshrc ou ~/.bashrc, ainsi que ce que Claude Code transmet explicitement. Si votre hook dépend de OPENAI_API_KEY dans .env, vous devrez lire .env manuellement.

Solution : lisez explicitement .env dans la commande du hook.

# Passing project env into hook:
cd "$PWD" && source .env && curl -H "Authorization: Bearer $OPENAI_API_KEY" ...
# Or with dotenv:
dotenv -e .env -- node my-validator.js

C’est particulièrement pénible dans un monorepo, où différents paquets ont chacun leur propre fichier .env. Pour résoudre le problème, transmettez PWD et exécutez explicitement cd "$PWD" au début de la commande.

Comment tester les hooks avant de créer un commit ?

À retenir

Un hook est une commande shell. Vous pouvez l’exécuter manuellement dans un terminal avec une entrée stdin fictive, vérifier son code de sortie et stderr, et faire tout cela avant que Claude Code ne l’exécute. Vous économiserez des heures de débogage et éviterez les hooks qui bloquent leur propre fonctionnement.

Scénario de test de base. Prenez la commande du hook dans le JSON, ouvrez un terminal et transmettez-lui le JSON que Claude Code enverrait :

# Test hook 1 (protect .env):
echo '{"tool_input":{"file_path":".env"}}' | bash -c '
FILE=$(jq -r ".tool_input.file_path // empty")
[ -z "$FILE" ] && exit 0
echo "$FILE" | grep -qE "(^|/)\.env(\.|$)" && {
  echo "BLOCKED" >&2
  exit 2
} || exit 0
'
echo "Exit code: $?"

Vous devriez voir BLOCKED dans stderr et Exit code: 2. Si vous obtenez exit 0, votre regex ne fonctionne pas ; corrigez grep.

Scénarios à vérifier :

  1. Cas nominal - le fichier doit être bloqué. .env pour le hook 1, git push --force main pour le hook 3.
  2. Cas négatif - le fichier ne doit pas être bloqué. README.md pour le hook 1, git push origin feature-branch pour le hook 3.
  3. Cas limites - entrée vide, valeurs nulles, caractères inattendus. Le hook doit soit renvoyer exit 0, soit échouer avec un message clair, sans laisser l’action passer sans bruit.

Où placer les hooks expérimentaux ? Pas dans .claude/settings.json, mais dans .claude/settings.local.json (hors de Git). Ajoutez-y les nouveaux hooks sous PostToolUse avec echo à la place de l’action réelle, vérifiez qu’ils se déclenchent, puis déplacez-les dans le settings.json de production.

// Experiment in settings.local.json - writes to /tmp/hook-debug.log:
{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit",
        "hooks": [
          {
            "type": "command",
            "command": "echo \"[hook fired] $(date) $(jq -r '.tool_input.file_path // \"?\"')\" >> /tmp/hook-debug.log"
          }
        ]
      }
    ]
  }
}

Après 5–10 modifications dans Claude Code, ouvrez /tmp/hook-debug.log pour voir quels fichiers ont déclenché le hook. Si le fichier est vide, le matcher ne convient pas ; corrigez le regex.

Et maintenant ?

À retenir

Commencez par des hooks PostToolUse qui renvoient exit 0 ; ce sont les plus sûrs et ils ne bloquent rien. Après une semaine, ajoutez votre premier hook PreToolUse avec exit 2 pour protéger .env ou empêcher les force push. Au bout de deux semaines, CLAUDE.md passera d’un contrat de 200 lignes à une courte description du contexte de 50–80 lignes, avec certains contrôles d’événements dans les hooks et des limites d’accès strictes appliquées séparément par permissions, sandbox et protections distantes.

5 étapes pour la semaine à venir :

  1. Recherchez dans votre CLAUDE.md des mots comme « jamais », « interdit », « toujours » et « doit ». Chacune de ces règles est candidate à un hook. Si une règle formule une obligation, le fichier texte ne la fera pas respecter.
  2. Commencez par PostToolUse + exit 0. C’est le plus sûr : ces hooks apportent une modification sans rien bloquer. prettier (hook 2), arrêter le serveur de développement (hook 5), créer automatiquement un commit après les tests (hook 6).
  3. N’ajoutez votre premier PreToolUse + exit 2 que lorsque vous êtes sûr de vous. Par exemple, protégez .env (hook 1) ou empêchez les force push (hook 3). C’est un blocage strict, et une erreur dans la commande grep peut vous bloquer vous-même.
  4. Testez les hooks manuellement avant de créer un commit (voir la section ci-dessus). Vous économiserez des heures de débogage.
  5. Gardez settings.local.json pour les expérimentations. Ajoutez-y les nouveaux hooks avec echo à la place de l’action réelle.

Une fois vos hooks configurés, l’étape suivante de cette série consiste à découvrir comment annuler des modifications dans Claude Code, au cas où un hook laisserait passer une action dangereuse. Si vous voulez que Claude se souvienne de la structure de votre projet entre les sessions, créez un Second Brain dans Claude Code (dossier business/ + base de connaissances liée). Et pour donner des noms aux skills de votre projet afin de les réutiliser, consultez Claude Code Skills.

Si vous avez besoin d’accéder à des réseaux neuronaux via Ailoot, découvrez Ailoot.

Plus de détails

Questions fréquentes : Hooks Claude Code : configuration et sept exemples

Pourquoi un hook doit-il lire son entrée JSON une seule fois ?

Stdin est un flux : le premier jq le consomme et les suivants lisent EOF. Conservez l’entrée une fois puis extrayez commande et résultat de cette copie. Un résultat absent ou inconnu reste non vérifié et doit empêcher le commit automatique.

Les hooks regex d’exemple créent-ils une barrière de sécurité ?

Non. Ils contrôlent certains événements et chaînes reconnues. Redirection shell, alias, scripts, MCP ou autre écriture peuvent les contourner ; utilisez identifiants restreints, permissions de fichiers, sandbox et branches protégées.

Tous les articles