Hooks Claude Code : lesquels ajoutent des tokens, lesquels en retirent

Un hook peut réduire ce que Claude lit ou alourdir chaque tour sans bruit. L'événement choisi et le champ renvoyé décident lequel des deux.

Profile photo of Paul Irolla

Par Paul Irolla

Founder · AI & developer tools · Tokenade

Docteur en IA · conçoit des outils d'optimisation de tokens pour agents de code

Voir la page de l'auteur
Mis à jour le 17 min de lecture
Résumer avec l'IA
Citer cette page

La référence des hooks de Claude Code liste désormais 33 événements, de SessionStart à ElicitationResult (référence des hooks d'Anthropic, lue le 24/09/2026). La plupart des gens découvrent les hooks via une liste de dix exemples à copier-coller : un linter après chaque édition, une notification sur le bureau, un garde-fou sur rm -rf. Puis ils lisent qu'un hook a réduit l'usage de tokens de quelqu'un de 42 % ou de 90 %, et se demandent si leurs propres hooks aident ou nuisent. Les deux arrivent. Un hook qui écrit sur stdout lors de UserPromptSubmit ajoute son texte à la conversation à chaque prompt envoyé. Un hook qui réécrit un appel d'outil avant son exécution peut empêcher un log de 3 000 lignes d'atteindre le modèle. La différence tient à deux détails que les listes d'exemples sautent : l'événement écouté par le hook, et le champ JSON qu'il renvoie. Cet article associe chaque événement qui pèse sur les tokens à ce qu'il ajoute ou retire, en s'appuyant uniquement sur la référence officielle, et liste les pièges qui laissent un hook ne rien faire sans prévenir.

TL;DR

  • Quatre événements ajoutent la sortie stdout brute au contexte de Claude : UserPromptSubmit, UserPromptExpansion, SessionStart et PostModelSwitch. Sur tous les autres, la sortie stdout d'un hook réussi part dans le log de débogage et ne coûte rien.
  • additionalContext est un coût en tokens par construction : Claude Code l'enveloppe dans un rappel système, l'insère là où le hook s'est déclenché, et il reste ensuite dans la conversation.
  • Deux champs retirent des tokens : updatedInput sur PreToolUse (réécrire l'appel avant son exécution) et updatedToolOutput sur PostToolUse (remplacer le résultat avant que Claude le lise).
  • Chaque chaîne injectée est plafonnée à 10 000 caractères ; au-delà, Claude reçoit un chemin de fichier et un aperçu de 2 000 caractères.
  • Le code de sortie 1 ne bloque pas, un hook PreToolUse qui dépasse son délai laisse passer l'appel, et une faute de frappe dans le chemin du script désactive un garde-fou sans rien arrêter.

Un hook, vu sous l'angle du coût

Un hook est un handler que Claude Code exécute à un point fixe d'une session : une commande shell, un endpoint HTTP, un outil d'un serveur MCP connecté, un prompt LLM ou un subagent (référence). La référence regroupe les événements par cadence. SessionStart et SessionEnd se déclenchent une fois par session. UserPromptSubmit, Stop et StopFailure une fois par tour. PreToolUse et PostToolUse à chaque appel d'outil dans la boucle agentique : leur nombre grandit avec chaque lecture de fichier, recherche et commande lancée par l'agent.

Cette cadence est la première moitié de la question du coût. Un handler qui se déclenche une fois par session et injecte 400 caractères vous coûte ces caractères une fois, plus leur relecture à chaque requête suivante. Les mêmes 400 caractères sur PreToolUse atterrissent à côté de chaque résultat d'outil de la session. Tout ce qui entre dans la conversation est renvoyé avec le reste de l'historique à chaque requête suivante au modèle, ce qui explique pourquoi le prompt caching compte autant pour les agents : les relectures en cache coûtent moins que l'entrée fraîche, mais elles ne sont jamais gratuites.

La seconde moitié, c'est ce que renvoie le handler. Un hook peut faire quatre choses avec les tokens :

  1. Rien. Il s'exécute, sort avec 0, et sa sortie stdout part dans le log de débogage. La plupart des hooks de formatage, de notification et de journalisation sont dans ce cas, et leur coût en tokens est nul.
  2. Ajouter du contexte : la sortie stdout brute sur les quatre événements qui l'acceptent, ou additionalContext en JSON.
  3. Retirer du contexte avant qu'il existe : réécrire les arguments d'un outil avec updatedInput, ou remplacer son résultat avec updatedToolOutput.
  4. Appeler un modèle : les hooks à base de prompt et à base d'agent envoient l'entrée du hook à Claude, ce qui fait une requête facturée à part.

La suite de cet article range les événements dans ces quatre cases. Si vous n'avez jamais mesuré où vont les tokens de votre session, commencez par là ; mesurer l'usage de tokens d'un agent montre comment lire les champs d'usage dans le transcript de session.

Les hooks qui ajoutent des tokens

La règle de Claude Code pour la sortie stdout brute est courte. Sur un code de sortie 0, « pour la plupart des événements, Claude Code écrit stdout dans le log de débogage et ne l'affiche pas dans le transcript. Les exceptions sont UserPromptSubmit, UserPromptExpansion, SessionStart et PostModelSwitch, où Claude Code ajoute la sortie stdout en texte brut comme contexte que Claude peut voir et exploiter » (référence, code de sortie 0).

Un echo dans un hook SessionStart est donc un petit ajout, une seule fois. Le même echo dans un hook UserPromptSubmit est un ajout à chaque prompt. La référence précise aussi que ni la sortie stdout brute ni additionalContext sur UserPromptSubmit ne produisent d'entrée visible dans le transcript : chacun est injecté comme un rappel système qui commence par le nom du hook. Vous payez un texte que vous ne voyez jamais à l'écran, et c'est la façon la plus courante dont une configuration de hooks fait grossir une facture sans que personne ne le remarque.

additionalContext en est la version structurée, et elle fonctionne sur plus d'événements. Pour PreToolUse et PostToolUse, c'est une « chaîne ajoutée au contexte de Claude à côté du résultat de l'outil ». Pour SessionStart, elle est ajoutée « au début de la conversation, avant le premier prompt ». Pour SubagentStart et PostModelSwitch, c'est la seule chose que le hook puisse faire : du contexte, sans blocage (référence, contrôle des décisions). Quand plusieurs hooks renvoient additionalContext pour le même événement, Claude reçoit toutes les valeurs.

Une troisième voie est moins évidente. Sur PostToolUse et PostToolUseFailure, sortir avec 2 « montre stderr à Claude ; l'outil a déjà tourné ». La référence le recommande pour faire remonter un avertissement depuis ces événements. Chaque avertissement est du texte dans la conversation : un hook de linter qui sort avec 2 et 80 lignes de remarques à chaque édition ajoute 80 lignes par édition.

Les hooks Stop coûtent autrement. Sortir avec 2 sur Stop « empêche Claude de s'arrêter et poursuit la conversation ». C'est un tour de modèle complet, avec tout l'historique renvoyé. Une vérification qui échoue à chaque tour fait tourner la session jusqu'à ce que vous interveniez. La variante à base de prompt a un garde-fou pour ça : quand le modèle renvoie impossible avec ok: false, Claude Code laisse le tour se terminer au lieu de renvoyer la raison. Les hooks de commande n'ont pas ce garde-fou ; c'est à vous de borner la boucle.

Le principe à retenir : injecter du contexte est acceptable quand le texte est court, qu'il change le prochain geste du modèle, et qu'il se déclenche à la cadence la plus basse qui fait encore le travail. Un nom de branche et le ticket ouvert sur SessionStart passent ce test. Un guide de style de 2 000 caractères sur chaque UserPromptSubmit a sa place dans CLAUDE.md, où il fait au moins partie du préfixe en cache.

Les hooks qui retirent des tokens

Deux champs permettent à un hook de réduire ce que lit le modèle, et ils agissent aux deux extrémités d'un appel d'outil.

updatedInput sur PreToolUse « modifie les paramètres d'entrée de l'outil avant l'exécution. Remplace l'objet d'entrée entier, donc incluez les champs inchangés avec les champs modifiés » (référence, contrôle des décisions de PreToolUse). C'est là que travaillent la plupart des hooks qui économisent des tokens. Un appel Bash à npm test peut être réécrit pour passer par un filtre qui ne garde que les échecs ; un Read d'un fichier de 5 000 lignes peut être restreint avec offset et limit ; un Grep sans chemin peut être limité à src/. Le modèle ne lit jamais que le résultat réduit, et rien de plus n'entre dans le contexte. Deux détails de la référence comptent ici. Les règles de permission sont évaluées « sur l'entrée que renvoie votre hook, pas sur celle que Claude a envoyée » : une réécriture ne peut donc pas faire passer une commande en douce devant une règle de refus. Et le remplacement porte sur l'objet entier : oubliez un champ, et l'outil tourne sans lui.

updatedToolOutput sur PostToolUse « remplace la sortie de l'outil par la valeur fournie avant qu'elle soit envoyée à Claude. La valeur doit respecter la forme de sortie de l'outil. » La référence le recommande désormais à la place de l'ancien updatedMCPToolOutput, qui ne fonctionne que sur les outils MCP. C'est le champ à utiliser quand vous ne pouvez pas savoir à l'avance la taille d'un résultat : laissez l'appel tourner, puis gardez les lignes d'erreur d'un log de build, retirez les barres de progression, ou réduisez une réponse JSON aux clés dont le modèle a besoin. L'exemple de la référence remplace le stdout d'un résultat Bash par une chaîne expurgée, ce qui montre la forme attendue.

Notez ce que fait, et ne fait pas, decision: "block" sur PostToolUse : il « ajoute la reason à côté du résultat de l'outil. Claude voit toujours la sortie d'origine ; pour la remplacer, utilisez updatedToolOutput. » Un hook PostToolUse bloquant censé masquer une énorme sortie ajoute du texte et ne retire rien.

Les résultats publiés pour cette approche sont des benchmarks d'une seule équipe : lisez-les comme des ordres de grandeur. SitePoint a rapporté le 20/08/2026 que Graft, un framework à base de hooks, a fait passer l'usage moyen de tokens de 8 070 à 4 650 par tâche sur les runs SWE-bench Verified internes de ses auteurs, soit une baisse de 42 % (SitePoint). Spotify Engineering a publié en septembre 2026 le témoignage d'un ingénieur intitulé « Portal by Spotify cut my Claude Code token usage by 90% », qui soutient que l'essentiel de ce que fait un agent de code relève des entrées-sorties, comme lire cinq fichiers pour répondre à une question sur une seule méthode (Spotify Engineering). Ni l'un ni l'autre n'est un test contrôlé sur votre dépôt. Le mécanisme derrière les deux est celui décrit plus haut, et la technique générale a sa propre page : le filtrage de sortie des logs de commandes.

Le plafond de 10 000 caractères

Chaque chaîne qu'un hook injecte a une limite stricte. La référence : « Les chaînes additionalContext, systemMessage et initialUserMessage d'un hook, ainsi que sa sortie stdout brute, sont plafonnées à 10 000 caractères. » Chaque champ JSON est mesuré séparément, la sortie stdout brute est mesurée en entier, et plusieurs hooks sur un même événement sont mesurés chacun de leur côté (référence, sortie JSON).

Au-delà de la limite, Claude Code ne tronque pas. Il « enregistre la sortie dans un fichier du dossier de session et la remplace par le chemin du fichier et un aperçu des 2 000 premiers caractères au plus ». Claude peut lire ce fichier, mais Claude Code ne le lui demande pas. Les gros résultats Bash valides sont traités de la même façon. Contrairement au plafond de Bash, celui des hooks « n'a ni réglage ni variable d'environnement pour le relever ».

Deux conséquences pour le coût :

  • Le plafond est une limite par chaîne, et rien ne vous empêche d'envoyer 9 999 caractères à chaque prompt. Il protège contre les hooks qui s'emballent, et un hook qui reste en dessous peut tout de même être le plus gros élément de votre contexte.
  • Dépasser le plafond peut aggraver les choses. Si le modèle décide qu'il a besoin du texte complet, il lit le fichier avec un appel d'outil, et vous payez l'aperçu, l'appel d'outil et le contenu complet.

Si un hook produit régulièrement plus de quelques centaines de caractères, la meilleure conception consiste en général à écrire vous-même les détails dans un fichier et à injecter une seule ligne qui y renvoie.

Les hooks qui appellent un modèle

Les hooks à base de prompt (type: "prompt") « envoient l'entrée du hook et votre prompt à un modèle Claude, Haiku par défaut » ; le champ model permet d'en changer (référence, hooks à base de prompt). Les hooks à base d'agent vont plus loin et lancent un subagent qui peut utiliser des outils avant de décider. Les deux sont des requêtes facturées en plus de votre session principale.

Ça change le calcul pour les événements à haute cadence. Un hook de prompt sur Stop tourne une fois par tour, ce qui est en général supportable. Un hook de prompt sur PreToolUse lance une requête au modèle à chaque appel d'outil, et chacune porte l'entrée du hook, y compris tous les arguments de l'outil. Pour une vérification qu'une expression régulière ou jq peut faire, un hook de commande ne coûte rien en tokens et répond en quelques millisecondes.

Réservez les hooks de prompt et d'agent aux cas où le jugement est tout l'intérêt : décider si une tâche est vraiment terminée avant de laisser Claude s'arrêter, ou si un diff touche quelque chose qu'une règle interdit. Gardez-les hors du chemin « un appel par outil », sauf si vous avez mesuré les requêtes supplémentaires. Les coûts du codage agentique suivent la même logique : chaque nouveau contexte de modèle démarre avec son propre surcoût.

PreModelSwitch : le premier signal de coût natif dans un hook

Longtemps, la plainte de l'issue GitHub #11008, ouverte en novembre 2025, est restée vraie : les hooks ne recevaient aucune donnée de tokens ou de coût, et un hook de budget devait analyser lui-même le transcript. PreModelSwitch change la donne pour un moment coûteux. Son entrée inclut context_tokens (les tokens que la requête suivante renvoie comme prompt), prompt_cache_warm, cache_ttl et estimated_cache_write_usd, le coût estimé de l'écriture de ce contexte dans le cache du nouveau modèle (référence, entrée de PreModelSwitch).

Pourquoi c'est important : un changement de modèle fait perdre un prompt cache chaud. L'exemple de la référence montre une requête /model opus depuis une session Sonnet 5 avec 182 340 tokens de contexte et une écriture de cache estimée à 1,14 $ au prix catalogue. Un hook peut renvoyer permissionDecision: "ask" avec ce chiffre dans la raison, pour que le changement se fasse avec le prix sous les yeux. Le délai par défaut de cet événement est de 30 secondes et, contrairement à PreToolUse, un hook PreModelSwitch qui dépasse son délai bloque le changement.

Pour tous les autres événements, le transcript reste la source. Chaque hook reçoit transcript_path, et la référence prévient que le fichier « est écrit de façon asynchrone et peut être en retard sur la conversation en mémoire ». Un hook de coût qui le lit doit s'attendre à ce que le dernier tour manque. Pour des chiffres par session sans écrire de hook, voir comment réduire l'usage de tokens de Claude Code, qui couvre les compteurs existants.

Erreurs fréquentes

  • Utiliser le code de sortie 1 pour bloquer. « Sans JSON valide sur stdout, Claude Code traite le code de sortie 1 comme une erreur non bloquante et poursuit l'action. » Les hooks de politique doivent sortir avec 2, ou renvoyer une décision JSON.
  • Faire confiance à un garde-fou lent. Un hook command, http ou mcp_tool sur PreToolUse qui atteint son délai « ne bloque pas l'appel d'outil ». Si votre filtre appelle quelque chose de lent, l'appel non filtré passe.
  • Une faute de frappe dans le chemin du script. Le shell sort avec 127, Claude Code affiche une notification non bloquante, et « un chemin mal tapé dans settings.json laisse le garde-fou désactivé sans bruit ». Surveillez la première exécution.
  • Cibler des outils MCP avec un préfixe nu. mcp__memory est comparé comme une chaîne exacte et ne correspond à aucun outil ; mcp__memory__.* correspond à tous les outils de ce serveur.
  • Afficher une bannière depuis le profil du shell. Stdout ne doit contenir que l'objet JSON. Un profil qui affiche quelque chose au démarrage casse l'analyse, et les champs JSON sont ignorés.
  • Attendre de suppressOutput une économie. La référence dit qu'il « n'a aucun effet » : Claude Code accepte le champ et n'en fait rien. La sortie stdout d'un hook réussi reste déjà hors du transcript sur la plupart des événements.

À faire cette semaine

  1. Listez vos hooks par événement. Ouvrez /hooks et notez chaque handler sur UserPromptSubmit, SessionStart, PreToolUse et PostToolUse. Pour chacun, notez s'il écrit du stdout brut, renvoie additionalContext ou sort avec 2 et du stderr. Ce sont vos hooks qui ajoutent des tokens.
  2. Déplacez le texte par prompt vers une cadence plus basse. Tout ce qui, sur UserPromptSubmit, ne change pas d'un prompt à l'autre va dans SessionStart ou dans CLAUDE.md. Résultat attendu : les mêmes consignes, payées une fois au lieu d'à chaque prompt.
  3. Ajoutez un hook qui réduit la sortie de votre outil le plus bavard. Repérez dans le transcript l'outil dont les résultats sont les plus gros (en général Bash qui lance des tests ou des builds) et ajoutez un hook PostToolUse qui renvoie updatedToolOutput avec seulement les erreurs et la ligne de résumé. Comparez le total des tokens d'entrée sur deux sessions similaires, avant et après.
  4. Faites sortir chaque hook de politique avec 2, et testez-le. Lancez une commande qu'il doit bloquer et vérifiez que le blocage apparaît. Un garde-fou que vous n'avez jamais vu se déclencher n'est pas un garde-fou.
  5. Ajoutez un hook PreModelSwitch avec "ask". Citez context_tokens et estimated_cache_write_usd dans la raison, pour qu'un changement de modèle en cours de session affiche son prix avant d'avoir lieu.

FAQ

Que sont les hooks de Claude Code ? Ce sont des handlers que Claude Code exécute automatiquement à des points fixes d'une session : avant et après les appels d'outils, à l'envoi d'un prompt, au démarrage ou à la fin d'une session, avant la compaction, et lors d'une trentaine d'autres événements. Un handler peut être une commande shell, un endpoint HTTP, un outil MCP, un prompt LLM ou un subagent. Il reçoit l'événement en JSON (sur stdin pour les commandes) et peut renvoyer une décision, du contexte supplémentaire, ou une entrée ou une sortie d'outil réécrite. On les configure dans les fichiers de réglages ou via le menu /hooks.

Les hooks de Claude Code consomment-ils des tokens ? Seulement quand leur sortie entre dans la conversation ou quand ils appellent un modèle. Sur la plupart des événements, la sortie stdout d'un hook réussi part dans le log de débogage et ne coûte rien. La sortie stdout brute sur UserPromptSubmit, UserPromptExpansion, SessionStart et PostModelSwitch est ajoutée au contexte, additionalContext est ajouté partout où il est pris en charge, et le stderr d'un hook PostToolUse qui sort avec 2 est montré à Claude. Les hooks de prompt et d'agent sont des requêtes au modèle à part, Haiku par défaut pour les hooks de prompt.

Un hook peut-il réduire l'usage de tokens de Claude Code ? Oui, grâce à deux champs. updatedInput sur PreToolUse réécrit un appel d'outil avant son exécution, par exemple pour restreindre une lecture de fichier ou faire passer une commande de test par un filtre. updatedToolOutput sur PostToolUse remplace le résultat d'un outil avant que Claude le lise. Les résultats publiés viennent de benchmarks d'une seule équipe : SitePoint a rapporté une baisse de 42 % par tâche pour Graft sur les runs SWE-bench de ses propres auteurs, en août 2026. Mesurez sur vos propres sessions avant de vous fier à un chiffre.

Quelle différence entre PreToolUse et PostToolUse ? PreToolUse se déclenche avant l'exécution d'un appel d'outil et peut le bloquer, demander une confirmation ou réécrire ses arguments avec updatedInput. PostToolUse se déclenche après la réussite d'un appel ; il ne peut pas l'annuler, mais il peut ajouter du contexte, afficher un avertissement via le code de sortie 2, ou remplacer le résultat avec updatedToolOutput. Pour économiser des tokens, PreToolUse évite de produire une grosse sortie, et PostToolUse traite une sortie dont vous ne pouvez pas prévoir la taille.

Pourquoi mon hook ne bloque-t-il rien ? Les causes habituelles : un code de sortie 1 au lieu de 2, un chemin de script qui n'existe pas, un matcher qui ne correspond à aucun outil (un préfixe MCP sans .*), du texte affiché avant le JSON sur stdout, ou un hook PreToolUse qui a dépassé son délai. La référence est explicite : un hook de commande sur PreToolUse qui dépasse son délai laisse l'appel d'outil continuer. Activez le log de débogage pour voir le message d'analyse ou de validation, et testez chaque garde-fou avec un appel qu'il doit arrêter.

Y a-t-il une limite de taille sur la sortie d'un hook ? Oui. additionalContext, systemMessage, initialUserMessage et la sortie stdout brute sont chacun plafonnés à 10 000 caractères. Au-delà, Claude Code écrit le texte dans un fichier du dossier de session et donne à Claude le chemin plus un aperçu de 2 000 caractères au plus. Aucun réglage ni variable d'environnement ne relève ce plafond. Les notes qu'un hook attache pour le classifieur du mode auto ont un plafond distinct de 2 000 caractères par appel d'outil.

Un hook peut-il voir combien de tokens la session a consommés ? Pas directement sur la plupart des événements : l'issue GitHub #11008 demandait des données de tokens et de coût dans les entrées des hooks. Chaque hook reçoit transcript_path, et on peut additionner les champs d'usage du transcript, en gardant en tête que le fichier est écrit de façon asynchrone et peut être en retard sur le dernier tour. PreModelSwitch est l'exception : son entrée porte context_tokens et estimated_cache_write_usd pour le changement qu'il s'apprête à autoriser ou bloquer.

Faut-il utiliser des hooks à base de prompt ? Utilisez-les là où un jugement est tout l'intérêt, par exemple pour vérifier avant Stop que la tâche est vraiment terminée. Évitez-les sur PreToolUse ou PostToolUse sauf si vous en avez mesuré le coût, parce qu'ils ajoutent une requête au modèle par appel d'outil. Tout ce qu'une expression régulière, jq ou un court script peut décider a sa place dans un hook de commande, qui ne coûte aucun token.

À lire aussi

FAQ

Réduisez la facture de tokens de votre agent IA.

Classé n°1 au Token-Harness Optimizer Leaderboard. Zéro config.

Tokenade réduit la facture de tokens des agents de code IA : une installation, zéro config, et il allège ce que votre agent envoie au modèle.

$ npm install -g @tokenade/cli
$ tokenade install
$ tokenade login