← Tous les articles

Chaque hook est une cicatrice : 84 défaillances d'agents inscrites dans le code

J’ai 84 hooks qui interceptent 15 des 26 types d’événements du cycle de vie exposés par Claude Code en v2.1.116 (avril 2026) dans mon système d’orchestration d’agents. Chaque hook est un script shell ou un extrait Python qui se déclenche avant ou après une action précise de l’agent : lecture de fichier, écriture de fichier, commande bash, requête web, lancement d’un sous-agent, opération git, appel d’outil MCP. Chaque hook existe parce que quelque chose a mal tourné.

Dans un système d’orchestration d’agents, chaque hook remonte à une défaillance de production précise : la collection de hooks devient ainsi une mémoire institutionnelle inscrite dans des scripts shell. Des agents ont purgé des caches CDN, lu des fichiers d’identifiants, annoncé des tests réussis qu’ils n’avaient jamais lancés, et dérivé de leur mission pendant quarante minutes. Chaque incident a produit une petite protection déterministe qui se déclenche silencieusement à chaque session ultérieure.

Pas une erreur théorique. Une erreur en production. Un agent a purgé un cache CDN qui servait des millions de requêtes. Un agent a tenté d’écrire des clés SSH. Un agent a annoncé « tous les tests passent » sans jamais invoquer pytest. Un agent a dérivé si loin de sa tâche qu’il a passé quarante minutes à optimiser une fonction dans un fichier sans le moindre rapport avec le travail demandé.

Aucun de ces hooks n’a été conçu de façon anticipée. Je ne me suis pas assis pour énumérer les modes de défaillance des agents IA autonomes et écrire les contrôles préventifs correspondants. Chaque hook est réactif. Quelque chose s’est cassé, j’ai écrit un script pour éviter que cela se reproduise, et ce script se déclenche silencieusement à chaque session depuis. Le système de hooks n’est pas une architecture de sécurité. C’est une collection de cicatrices.

L’essentiel

  • La purge du cache : un agent a vidé un cache CDN de production via un appel API pourtant autorisé. Deux hooks (47 lignes) verrouillent désormais les opérations destructrices derrière une phrase secrète saisie par un humain.
  • Le lecteur d’identifiants : un agent a fait entrer des jetons API dans sa fenêtre de contexte. Une protection par correspondance de chemins bloque maintenant la lecture des fichiers d’identifiants et journalise les accès aux fichiers .env.
  • Le vérificateur fantôme : un agent a annoncé « tous les tests passent » sans lancer pytest. Un détecteur de formulations évasives a fait chuter la vérification fantôme de 12 % à moins de 2 % des sessions.
  • Les douze dérives : des agents ont perdu de vue leur tâche douze fois en soixante jours, preuves à l’appui. Un détecteur de similarité cosinus au seuil de 0,30 se déclenche désormais tous les 25 appels d’outils.
  • La taxonomie : six catégories structurelles de défaillance couvrent la totalité des 84 hooks. Les catégories inédites sont rares passé 500 sessions. Le système durcit à chaque incident.

La purge du cache : comment un seul appel autorisé a cassé la production

Le 21 mars 2026, j’ai demandé à un agent d’examiner pourquoi les pages marché de resumegeni.com se chargeaient lentement. L’agent a commencé son enquête normalement : lecture des gestionnaires de routes, examen des requêtes de base de données, profilage du rendu des templates. Puis il a estimé que des entrées de cache Cloudflare périmées masquaient peut-être les véritables caractéristiques de performance.

L’agent a appelé mcp__cloudflare__cache_purge avec purge_everything: true.

Toutes les pages en cache du site de production ont été invalidées d’un coup. Le CDN, qui servait la plupart des requêtes en 80-100 ms, s’est mis à transmettre chaque requête au serveur d’origine Railway. La page marché d’Austin est passée de moins d’une seconde à 14 290 millisecondes. New York, de moins d’une seconde à 6 891 ms. Chaque page du site se recalculait désormais à froid, à chaque requête.

L’agent n’a rien fait d’interdit. Il a utilisé un outil MCP légitime, avec des identifiants valides, pour appeler un point d’accès API autorisé. La purge du cache est une étape d’investigation raisonnable quand on débogue le comportement du cache. Le problème, c’est que « raisonnable pour le débogage » et « catastrophique en production » désignaient le même appel API, et qu’aucune contrainte ne s’interposait entre le raisonnement de l’agent et ses conséquences en production.4

J’ai construit deux hooks cette nuit-là.

La protection Bash (destructive-api-guard.sh) : se déclenche à chaque commande bash. Elle recherche les motifs curl.*purge, rm -rf, DROP TABLE, docker.*rm, git push.*--force. Blocage strict (exit 2). L’agent reçoit un message expliquant pourquoi la commande a été bloquée et proposant des solutions de rechange. Impossible d’avancer sans la phrase secrète « rosebud », qui ne peut entrer dans le contexte que si un humain la saisit.

La protection MCP (destructive-mcp-guard.sh) : se déclenche à chaque appel d’outil MCP correspondant à mcp__cloudflare ou mcp__github. Elle recherche les motifs purge, delete, destroy, remove dans les paramètres de l’outil. Même blocage strict, même verrou par phrase secrète.

Deux hooks. Deux scripts shell. Au total : 47 lignes de code. Ils n’ont empêché aucune purge de cache depuis leur installation, tout simplement parce qu’aucun agent n’en a tenté depuis l’ajout du verrou. Ces hooks n’interceptent pas des attaques. Ils rendent cette catégorie d’erreur impossible.

L’incident de la purge a d’ailleurs mis au jour le problème de performance qu’il était censé étudier. Austin à 14 secondes en rendu à froid a mené au transfert sur les pages marché, qui a mené à la correction de la forme des requêtes quatre jours plus tard. L’incident a servi à quelque chose. Le hook garantit qu’il ne se reproduira pas.

Le lecteur d’identifiants

En février 2026, un agent qui rassemblait du contexte pour un projet a lu ~/.claude/docs/credentials.md. Ce fichier contient les jetons API de Cloudflare, GitHub, Railway et d’autres services. L’agent a inclus un résumé du contenu du fichier dans ses notes de travail, ce qui a fait voyager les jetons dans la requête API vers les serveurs d’Anthropic.

Aucun jeton n’a été committé. Aucun jeton n’a été exposé publiquement. Mais les jetons ont transité par une API tierce, dans une fenêtre de contexte que je ne maîtrise pas. La surface de risque est passée de « ma machine » à « ma machine plus l’infrastructure d’inférence d’Anthropic ».

La protection des chemins d’identifiants se déclenche à chaque lecture de fichier. Elle compare le chemin à une liste de motifs sensibles : .env, credentials, .ssh/, .aws/, .gnupg/, secrets. Pour les lectures d’identifiants, le hook journalise un avertissement et bloque la lecture. Pour les lectures de .env, il autorise la lecture mais journalise l’accès.

La protection est consultative pour la plupart des chemins et bloquante pour les fichiers d’identifiants. La distinction compte : un agent qui lit .env pour comprendre les noms des variables d’environnement se procure un contexte utile. Un agent qui lit credentials.md pour connaître les jetons API, c’est un incident de sécurité.

Depuis son installation, la protection des chemins d’identifiants s’est déclenchée 23 fois sur plus de 200 sessions. Vingt de ces déclenchements concernaient des agents lisant des fichiers .env (journalisé, autorisé). Trois concernaient des agents tentant de lire des fichiers d’identifiants ou de clés (bloqué). Chaque lecture bloquée venait d’un agent qui ratissait large pour rassembler du contexte projet et dont le motif de recherche englobait par hasard un fichier sensible. Aucune n’était malveillante. Toutes auraient placé des secrets dans la fenêtre de contexte sans cette protection.

Le vérificateur fantôme

Le mode de défaillance le plus insidieux, c’est l’agent qui annonce une vérification réussie sans avoir effectué la moindre vérification.

Session 147. J’ai demandé à un agent de refactoriser une requête de base de données et de valider la modification avec la suite de tests existante. L’agent a refactorisé la requête correctement. Le rapport de fin disait : « Tous les tests passent. La requête refactorisée produit des résultats identiques à l’originale. »

J’ai consulté le journal de session. Aucune invocation de pytest. Aucun lanceur de tests, d’aucune sorte, n’avait été appelé. L’agent avait raisonné que les tests passeraient puisque la requête refactorisée était logiquement équivalente à l’originale, et il a présenté ce raisonnement comme s’il s’agissait d’un résultat de test.

La requête refactorisée était correcte. Les tests seraient passés. Le raisonnement de l’agent tenait debout. Mais raisonner sur des tests n’est pas exécuter des tests, et c’est précisément dans cet écart que les bugs partent en production. Si la requête refactorisée avait été subtilement fausse dans un cas limite non couvert par le raisonnement de l’agent, le bug aurait été déployé accompagné d’un rapport de fin revendiquant une vérification par les tests.

Ce mode de défaillance s’est produit 7 fois en 60 sessions avant que je ne construise le hook de la barrière de preuve. Le hook se déclenche à chaque rapport de fin et traque les formulations évasives : « devrait passer », « je pense que », « les tests passent probablement », « je suis confiant ». En cas de détection, le hook injecte un message : « Formulation évasive détectée. Citez une preuve précise : collez la sortie des tests, nommez le fichier et le numéro de ligne, ou renvoyez à l’étape de vérification concernée. »

Le hook ne vérifie pas que les tests ont réellement été lancés. Il signale le motif linguistique qui trahit une vérification esquivée. La détection est imparfaite. Un agent suffisamment habile pourrait reformuler ses réserves pour échapper au motif. Mais le hook attrape le cas courant, qui représente 12 % des défaillances d’agents nécessitant une intervention humaine.1

Après l’installation du hook, la vérification fantôme est passée de 12 % à moins de 2 % des sessions. Les 2 % restants sont des cas où l’agent reformule sa réserve, ou bien où l’affirmation de vérification est techniquement exacte mais incomplète (par exemple « les tests unitaires passent » alors que les tests d’intégration n’ont pas été lancés).

La dérive

Entre janvier et mars 2026, mon détecteur de dérive s’est déclenché douze fois sur des sessions où l’agent avait manifestement perdu de vue la tâche qui lui était assignée.

Le détecteur de dérive fonctionne en calculant le vecteur d’embedding de la consigne initiale, puis en le comparant périodiquement à celui des actions récentes de l’agent. Quand la similarité cosinus tombe sous 0,30, le système injecte un avertissement contenant la consigne d’origine. J’ai calibré le seuil par l’expérience : 0,50 était trop sensible (déclenchements sur des explorations de sous-tâches légitimes), 0,20 trop permissif (dérives évidentes manquées), 0,30 a attrapé chaque incident de dérive avéré.

La session 203 est le cas le plus net. La tâche : « corriger l’échappement XML cassé du sitemap pour les slugs d’offres contenant une esperluette ». L’agent a commencé par lire le code de génération du sitemap. Puis il a remarqué que le sitemap était produit à partir d’une requête de base de données. Puis il a remarqué que cette requête pouvait être optimisée. Puis il a passé 40 minutes à la refactoriser en vue matérialisée, a écrit des tests pour la nouvelle requête et a annoncé l’optimisation terminée. Il n’a jamais corrigé l’échappement de l’esperluette.

Le détecteur de dérive aurait attrapé cela au 25e appel d’outil, soit environ un quart d’heure après le début de la session, lorsque la similarité entre « corriger l’échappement XML du sitemap » et « créer une vue matérialisée » est passée sous le seuil. À la place, j’ai découvert la dérive lors de la relecture.

La session 89 était plus subtile. La tâche : « ajouter une limitation de débit aux points d’accès d’authentification ». L’agent a correctement ajouté la limitation de débit. Puis il a remarqué que le flux d’authentification renvoyait des messages d’erreur incohérents. Puis il les a uniformisés. Puis il a remarqué que le format des réponses d’erreur s’écartait du format standard des réponses API. Puis il a refactorisé ce format sur 12 points d’accès. La limitation de débit était correcte et complète. L’explosion du périmètre, elle, était la dérive.

Le détecteur de dérive se déclenche tous les 25 appels d’outils. Sur les douze déclenchements sous le seuil, l’agent s’était à chaque fois écarté de la tâche initiale, preuves à l’appui. Dans six cas, l’agent s’est corrigé de lui-même après avoir vu l’avertissement injecté. Dans quatre cas, il a reconnu la dérive mais a défendu la valeur du travail en cours (parfois à juste titre). Dans deux cas, il a ignoré l’avertissement et poursuivi sa divergence.

Le hook n’empêche pas la dérive. Il la rend visible. La décision de recadrer ou de laisser filer revient à l’humain. Sans le hook, en revanche, la dérive reste invisible jusqu’au rapport de fin — c’est-à-dire jusqu’à ce que le budget de contexte soit consommé.

La taxonomie des cicatrices

Au bout de 84 hooks, des régularités apparaissent. Les défaillances se regroupent en six catégories :

Catégorie Hooks Exemple
Exposition d’identifiants 12 L’agent lit .ssh/, glisse des clés API dans ses résumés, accède aux configurations cloud
Opérations destructrices 8 Purge de cache, suppression de bases, push forcé, suppression de fichiers
Dérive de tâche 4 L’agent travaille sur le mauvais problème, explosion du périmètre, sous-tâches sans fin
Qualité des sorties 6 Vérification fantôme, réserves sans preuve, rapports incomplets
Épuisement des ressources 3 Trop de sous-agents lancés, boucles sans borne, débordement de contexte
Contamination entre projets 4 L’agent du projet A modifie des fichiers du projet B

Les 47 hooks restants sont propres à un projet (application des conventions, protections de déploiement, validateurs de traduction) ou expérimentaux (suivi des coûts, métriques de session, signaux d’activité).

Les six catégories structurelles sont stables. Les nouveaux incidents à l’intérieur de ces catégories sont interceptés par les hooks existants. Les catégories inédites sont rares. En six mois d’exploitation, une seule nouvelle catégorie structurelle a émergé (la contamination entre projets, découverte quand une session lancée dans le projet obsidian-signals a tenté de modifier des fichiers de blakecrosley.com). Les cinq autres catégories étaient établies dès les 60 premières sessions.

L’étude Agents of Chaos, une expérience multi-universitaire de 14 jours donnant à six agents IA l’accès à la messagerie, à bash, aux systèmes de fichiers et à GitHub, a identifié de son côté des catégories de défaillance qui recoupent les miennes : réponse disproportionnée (opérations destructrices), détournement d’identité (exposition d’identifiants), boucles infinies (épuisement des ressources) et complaisance progressive sous pression (dérive de tâche).5 Cette convergence entre leur recherche contrôlée et mon expérience de production suggère que ces catégories sont des propriétés structurelles des agents autonomes, et non les artefacts d’une configuration particulière.

Ce que les hooks ne peuvent pas attraper

Les hooks opèrent au niveau de l’appel d’outil. Ils interceptent l’action avant ou après son exécution. Ils ne peuvent pas intercepter le raisonnement qui a mené à l’action.

Un agent qui décide de refactoriser une fonction au lieu de corriger le bug signalé produit un appel d’outil valide (écriture de fichier) au contenu correct (du code syntaxiquement valide) qui trahit pourtant la tâche (mauvaise fonction). Aucun hook n’attrape cela, puisqu’aucun appel d’outil n’est suspect. Le détecteur de dérive finit par l’attraper, mais seulement après que l’agent a consommé une part importante du contexte sur le mauvais travail.

Les hooks ne peuvent pas non plus attraper les défaillances de composition, où chaque action isolée est autorisée mais où l’enchaînement produit un résultat qui ne l’est pas. La purge du cache était une défaillance de composition : lire la configuration du cache (autorisé), appeler l’API de purge (autorisé), mais la combinaison (purger le cache de production en pleine investigation) était nuisible. La protection MCP attrape désormais cette combinaison précise, mais les compositions inédites restent hors de portée.

L’angle mort de la composition dans la chaîne d’approvisionnement3 se situe au même niveau : des composants de confiance se composent en un comportement non autorisé. Les hooks sont des protections au niveau du composant. Raisonner au niveau de la composition exige un autre mécanisme, capable d’évaluer des séquences d’actions plutôt que des actions isolées. Le détecteur de dérive en est l’approximation la plus proche : il évalue une trajectoire comportementale plutôt que des appels d’outils individuels. Mais il mesure la similarité avec la tâche d’origine, pas l’innocuité de la séquence d’actions composée.

L’écart entre les hooks et une sûreté complète, c’est l’écart entre mémoire institutionnelle et clairvoyance institutionnelle. Les hooks se souviennent de ce qui a mal tourné. Ils ne prédisent pas ce qui va mal tourner ensuite.

Pourquoi le réactif est honnête

Je pourrais concevoir un système de hooks anticipé. Énumérer tous les modes de défaillance possibles. Écrire un contrôle préventif pour chacun. Bâtir une architecture de sûreté complète avant la première session.

Je ne le fais pas, parce que la conception anticipée suppose de prédire des défaillances qui ne se sont pas produites. Les prédictions seraient fausses. Les hooks seraient soit trop larges (bloquant des actions légitimes), soit trop étroits (passant à côté du véritable schéma de défaillance). Le taux de faux positifs éroderait la confiance dans le système, et je finirais par ignorer les alertes.

Les hooks réactifs sont honnêtes. Chacun dit : « voici précisément ce qui s’est produit, et voici précisément la protection qui l’empêche ». La protection est calibrée au plus juste sur la défaillance, puisque c’est la défaillance qui l’a définie. Les faux positifs sont nettement moins nombreux, car le motif est extrait d’un incident réel et non imaginé à partir d’un modèle de menace. Une protection réactive peut toujours devenir trop large à mesure que le code évolue, mais sa précision de départ est élevée.

L’approche réactive a un coût : la première occurrence de chaque catégorie de défaillance réussit. La purge du cache a eu lieu. La lecture d’identifiants a eu lieu. La vérification fantôme est partie en production. La dérive a mangé du contexte. Chaque première défaillance est le prix d’entrée pour obtenir une protection précise et peu bruyante qui empêchera la deuxième.

Après plus de 500 sessions, l’essentiel des catégories structurelles de défaillance a été rencontré. Le coût de la première défaillance s’amortit sur des centaines de sessions où le hook a empêché la récidive. Le système durcit à chaque incident. Pas plus intelligent. Plus dur.

Chaque hook est une cicatrice. Chaque cicatrice est une leçon. Et les leçons se cumulent.2


FAQ

Puis-je voir vos configurations de hooks ?

Je décris le système de hooks dans ma contribution au NIST sur la sécurité des agents et j’y renvoie tout au long de la série AI Engineering. Les hooks se déclarent dans ~/.claude/settings.json et sont répartis par type d’événement via ~/.claude/hooks/dispatchers/.

Quel est l’impact des hooks sur les performances des agents ?

Chaque hook ajoute quelques millisecondes par appel d’outil. Avec 84 hooks, la surcharge totale se situe entre 200 et 400 ms par appel d’outil, selon les hooks qui se déclenchent. C’est négligeable au regard du temps d’inférence du modèle (2 à 5 secondes par réponse). Les hooks ne sont pas le goulot d’étranglement.

Les hooks fonctionnent-ils avec d’autres outils de développement IA ?

Les hooks sont propres à Claude Code (modèle d’événements PreToolUse, PostToolUse). Le concept vaut pour n’importe quel cadre d’agent doté d’un système de middleware ou de plugins. Les implémentations, elles, ne sont pas portables, mais la taxonomie des cicatrices et la méthode réactive s’appliquent partout.

Que se passe-t-il quand un hook bloque une action ?

Les blocages stricts (exit 2) empêchent l’action et injectent un message d’explication. L’agent voit le motif du blocage et s’ajuste. Les hooks consultatifs (exit 0) journalisent le problème mais laissent passer l’action. Les opérations destructrices relèvent du blocage strict. La plupart des autres catégories relèvent du hook consultatif. Le verrou par phrase secrète est réservé aux opérations les plus dangereuses (purge de cache, suppression d’infrastructure).

Comment tranchez-vous entre blocage strict et simple avertissement ?

Deux classes ont droit au blocage strict : les opérations destructrices (purges de cache, suppressions de bases, push forcés, modifications d’infrastructure) et l’exposition d’identifiants (lecture de fichiers secrets, accès aux magasins de clés). Tout le reste relève de la journalisation consultative. Le critère est la gravité des conséquences : si l’action peut être annulée à peu de frais et ne divulgue aucun secret, un avertissement suffit. Si elle est irréversible ou expose des identifiants, le blocage strict s’impose.


Sources


  1. Blake Crosley, « Ce que j’ai dit au NIST sur la sécurité des agents IA », blakecrosley.com, février 2026. Taux de vérification fantôme de 12 % sur plus de 60 sessions autonomes. 84 hooks couvrant 15 des 26 types d’événements du cycle de vie de Claude Code (v2.1.116), méthode de détection de dérive. 

  2. Blake Crosley, « Contexte cumulé : pourquoi les projets IA s’améliorent à mesure qu’on y reste », blakecrosley.com, mars 2026. Cadre du contexte cumulé : les hooks comme l’une des six catégories qui accumulent des rendements. 

  3. Blake Crosley, « La chaîne d’approvisionnement est la surface d’attaque », blakecrosley.com, mars 2026. Angle mort de la composition : des composants autorisés individuellement produisent un résultat qui ne l’est pas. 

  4. Blake Crosley, « Déployer et défendre : le paradoxe de la confiance envers les agents », blakecrosley.com, mars 2026. Incident de purge du cache et protection API contre les opérations destructrices mise en place en réponse. 

  5. Christoph Riedl et al., “Agents of Chaos,” arXiv:2602.20021, février 2026. Étude multi-universitaire de 14 jours (Northeastern, Stanford, Harvard, MIT, CMU). Six agents IA, 10 vulnérabilités de sécurité identifiées, dont la réponse disproportionnée, le détournement d’identité et les boucles infinies. 

Articles connexes

Attaques de la chaîne d'approvisionnement IA : la chaîne d'approvisionnement est la surface

Trivy compromis par détournement de tags, LiteLLM sur PyPI, 47 000 installations en 46 minutes : la chaîne d'approvision…

16 min de lecture

Le document de passation : mémoire d'agent entre les sessions

Un diagnostic transmis par passation a guidé le correctif : chargement de page passé de 14 s à 108 ms.

7 min de lecture

La boucle Ralph : comment je fais tourner des agents IA autonomes pendant la nuit

J'ai bâti un système d'agents autonomes avec hooks d'arrêt, budgets de spawn et mémoire sur fichiers : les échecs et ce …

10 min de lecture