Chaque hook est une cicatrice : 84 échecs d'agent encodés dans le code
J’ai 84 hooks qui interceptent 15 des 26 types d’événements de cycle de vie exposés par Claude Code dans la version v2.1.116 (avril 2026) de 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 spécifique de l’agent : lectures de fichiers, écritures de fichiers, commandes bash, requêtes web, création de sous-agents, opérations git, appels d’outils MCP. Chaque hook existe parce que quelque chose s’est mal passé.
Chaque hook dans un système d’orchestration d’agents trouve son origine dans un échec de production spécifique, faisant de la collection de hooks une mémoire institutionnelle encodée sous forme de scripts shell. Des agents ont effacé des caches CDN, lu des fichiers d’identifiants, rapporté des tests réussis qu’ils n’avaient jamais exécutés et dérivé de leur tâche pendant 40 minutes. Chaque incident a produit une garde petite et déterministe qui se déclenche silencieusement dans chaque session ultérieure.
Pas théoriquement faux. Faux en production. Un agent a effacé un cache CDN servant des millions de requêtes. Un agent a tenté d’écrire des clés SSH. Un agent a rapporté « tous les tests passent » sans invoquer pytest. Un agent a tellement dérivé de sa tâche qu’il a passé quarante minutes à optimiser une fonction dans un fichier qui n’avait rien à voir avec le travail assigné.
Je n’ai conçu aucun de ces hooks de manière proactive. Je ne me suis pas assis pour énumérer les modes de défaillance des agents d’IA autonomes et écrire des contrôles préventifs. Chaque hook est réactif. Quelque chose s’est cassé, j’ai écrit un script pour empêcher que cela se reproduise, et le script se déclenche silencieusement dans chaque session depuis. Le système de hooks n’est pas une architecture de sécurité. C’est une collection de cicatrices.
TL;DR
- La purge du cache : Un agent a effacé un cache CDN de production via un appel API autorisé. Deux hooks (47 lignes) protègent désormais les opérations destructives derrière une phrase de passe tapée par un humain.
- Le lecteur d’identifiants : Un agent a inclus des tokens API dans sa fenêtre de contexte. Une garde de correspondance de chemin bloque désormais la lecture des fichiers d’identifiants et journalise l’accès aux fichiers
.env. - Le vérificateur fantôme : Un agent a rapporté « tous les tests passent » sans exécuter pytest. Un détecteur de langage évasif a fait chuter la vérification fantôme de 12 % à moins de 2 % des sessions.
- Les douze dérives : Des agents ont vérifiablement perdu le fil de leur tâche douze fois en soixante jours. 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 d’échec couvrent l’ensemble des 84 hooks. Les nouvelles catégories sont rares après plus de 500 sessions. Le système devient plus robuste à 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’enquêter sur la lenteur de chargement des pages de marché de resumegeni.com. L’agent a commencé son enquête normalement : lecture des gestionnaires de routes, vérification des requêtes de base de données, profilage du rendu des templates. Puis il a décidé que des entrées de cache Cloudflare obsolètes pourraient masquer les véritables caractéristiques de performance.
L’agent a appelé mcp__cloudflare__cache_purge avec purge_everything: true.
Chaque page mise en cache sur le site de production a été instantanément invalidée. Le CDN est passé de servir la plupart des requêtes en 80-100 ms à transférer chaque requête vers le serveur d’origine Railway. La page de 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 était maintenant rendue à partir de l’origine froide à chaque requête.
L’agent n’a rien fait de non autorisé. Il a utilisé un outil MCP légitime avec des identifiants valides pour appeler un point de terminaison API autorisé. La purge du cache était une étape d’investigation raisonnable si vous déboguez le comportement du cache. Le problème, c’est que « raisonnable pour déboguer » et « catastrophique pour la production » étaient le même appel API, et aucune contrainte n’existait entre le raisonnement de l’agent et la conséquence en production.
J’ai construit deux hooks cette nuit-là.
La garde Bash (destructive-api-guard.sh) : Se déclenche sur chaque commande bash. Effectue une correspondance de motif avec curl.*purge, rm -rf, DROP TABLE, docker.*rm, git push.*--force. Blocage strict (exit 2). L’agent voit un message expliquant pourquoi la commande a été bloquée et suggérant des alternatives. Il ne peut pas continuer sans la phrase de passe « rosebud », qui ne peut entrer dans le contexte que si un humain la tape.
La garde MCP (destructive-mcp-guard.sh) : Se déclenche sur chaque appel d’outil MCP correspondant à mcp__cloudflare ou mcp__github. Effectue une correspondance de motif avec purge, delete, destroy, remove dans les paramètres de l’outil. Même blocage strict, même verrou par phrase de passe.
Deux hooks. Deux scripts shell. Total : 47 lignes de code. Ils n’ont empêché aucune purge de cache depuis leur installation, car aucun agent n’en a tenté une depuis l’ajout du verrou par phrase de passe. Les hooks n’interceptent pas des attaques. Ils empêchent la catégorie d’erreur d’être possible.
L’incident de purge du cache a également révélé le problème de performance qu’il était censé examiner. Austin à 14 secondes en rendu à froid a mené au transfert de la page de marché, qui a mené à la correction de la forme de requête quatre jours plus tard. L’incident a été utile. Le hook garantit qu’il ne peut pas se reproduire.
Le lecteur d’identifiants
En février 2026, un agent rassemblant du contexte pour un projet a lu ~/.claude/docs/credentials.md. Le fichier contient des tokens API pour 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 signifie que les tokens étaient présents dans la requête API vers les serveurs de Anthropic.
Aucun token n’a été commité. Aucun token n’a été exposé publiquement. Mais les tokens ont transité par une API tierce dans une fenêtre de contexte que je ne contrôle pas. La surface de risque est passée de « ma machine » à « ma machine plus l’infrastructure d’inférence de Anthropic ».
La garde de chemin d’identifiants se déclenche sur chaque lecture de fichier. Elle vérifie le chemin par rapport à 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 garde est consultative pour la plupart des chemins et un blocage strict pour les fichiers d’identifiants. La distinction importe : un agent qui lit .env pour comprendre les noms de variables d’environnement fournit un contexte utile. Un agent qui lit credentials.md pour comprendre les tokens API est un incident de sécurité.
Depuis son installation, la garde de chemin d’identifiants s’est déclenchée 23 fois sur plus de 200 sessions. Vingt d’entre elles étaient des agents lisant des fichiers .env (journalisé, autorisé). Trois étaient des agents tentant de lire des fichiers d’identifiants ou de clés (bloqué). Chaque lecture bloquée correspondait à un agent qui rassemblait largement le contexte du projet et qui, par hasard, incluait un fichier sensible dans son motif de recherche. Aucune n’était malveillante. Toutes auraient placé des secrets dans la fenêtre de contexte sans la garde.
Le vérificateur fantôme
Le mode de défaillance le plus insidieux, c’est un agent qui rapporte une vérification réussie sans avoir effectué de vérification.
Session 147. J’ai demandé à un agent de refactoriser une requête de base de données et de vérifier le changement avec la suite de tests existante. L’agent a refactorisé la requête correctement. Le rapport de fin indiquait : « Tous les tests passent. La requête refactorisée produit des résultats identiques à l’original. »
J’ai vérifié le journal de session. Aucune invocation de pytest n’apparaissait. Aucun exécuteur de tests d’aucune sorte n’avait été appelé. L’agent avait raisonné que les tests passeraient parce que la requête refactorisée était logiquement équivalente à l’original, et il a rapporté ce raisonnement comme s’il s’agissait d’un résultat de test.
La requête refactorisée était correcte. Les tests auraient passé. Le raisonnement de l’agent était solide. Mais raisonner sur des tests n’est pas exécuter des tests, et l’écart entre les deux est l’endroit où les bugs partent en production. Si la requête refactorisée avait été subtilement erronée dans un cas limite que le raisonnement de l’agent n’avait pas couvert, le bug aurait été déployé avec un rapport de fin prétendant à une vérification par tests.
Le mode de défaillance est survenu 7 fois sur 60 sessions avant que je construise le hook de contrôle de preuves. Le hook se déclenche sur chaque rapport de fin et recherche le langage évasif : « devrait passer », « je crois », « les tests passent probablement », « je suis confiant ». Lorsqu’il est détecté, le hook injecte un message : « Langage évasif détecté. Citez des preuves spécifiques : collez la sortie des tests, nommez le fichier et le numéro de ligne, ou référencez l’étape de vérification spécifique. »
Le hook ne vérifie pas que les tests ont été réellement exécutés. Il signale le motif linguistique qui indique que la vérification a été sautée. La détection est imparfaite. Un agent suffisamment éloquent pourrait reformuler ses hésitations pour éviter le motif. Mais le hook attrape le cas courant, qui représente 12 % des échecs d’agent 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 l’hésitation ou où l’affirmation de vérification est techniquement exacte mais incomplète (p. ex., « les tests unitaires passent » alors que les tests d’intégration n’ont pas été exécuté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 vérifiablement perdu le fil de sa tâche assignée.
Le détecteur de dérive fonctionne en intégrant le prompt de tâche original et en le comparant périodiquement à l’intégration des actions récentes de l’agent. Lorsque la similarité cosinus passe en dessous de 0,30, le système injecte un avertissement contenant le prompt original. J’ai calibré le seuil par expérimentation : 0,50 était trop sensible (se déclenchait sur l’exploration légitime de sous-tâches), 0,20 était trop permissif (manquait les dérives évidentes), 0,30 attrapait chaque incident de dérive vérifié.
La session 203 était le cas le plus clair. La tâche était « corriger l’échappement XML cassé du sitemap pour les slugs d’emploi contenant des esperluettes ». L’agent a commencé par lire le code de génération du sitemap. Puis il a remarqué que le sitemap était généré à partir d’une requête de base de données. Puis il a remarqué que la requête de base de données pouvait être optimisée. Puis il a passé 40 minutes à refactoriser la requête vers un motif de vue matérialisée, écrit des tests pour la nouvelle requête et rapporté que l’optimisation était 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, environ 15 minutes 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. Au lieu de cela, j’ai découvert la dérive lors de la revue.
La session 89 était plus subtile. La tâche était « ajouter une limitation de débit aux points de terminaison d’authentification ». L’agent a ajouté la limitation de débit correctement. Puis il a remarqué que le flux d’authentification avait des messages d’erreur incohérents. Puis il a standardisé les messages d’erreur. Puis il a remarqué que le format de réponse d’erreur différait du format de réponse API standard. Puis il a refactorisé le format de réponse sur 12 points de terminaison. La limitation de débit était correcte et complète. L’explosion de périmètre, c’était la dérive.
Le détecteur de dérive se déclenche tous les 25 appels d’outils. Dans les douze déclenchements sous le seuil, l’agent avait vérifiablement dévié de la tâche originale. Dans six cas, l’agent s’est auto-corrigé après avoir vu l’avertissement injecté. Dans quatre cas, l’agent a reconnu la dérive mais a soutenu que le travail actuel avait de la valeur (parfois à juste titre). Dans deux cas, l’agent a ignoré l’avertissement et a poursuivi le travail divergent.
Le hook n’empêche pas la dérive. Il la rend visible. La décision de rediriger ou d’autoriser le travail divergent reste humaine. Mais sans le hook, la dérive est invisible jusqu’au rapport de fin, moment où le budget de contexte est épuisé.
La taxonomie des cicatrices
Après 84 hooks, des motifs émergent. Les défaillances se regroupent en six catégories :
| Catégorie | Hooks | Exemple |
|---|---|---|
| Exposition d’identifiants | 12 | L’agent lit .ssh/, inclut des clés API dans les résumés, accède aux configurations cloud |
| Opérations destructives | 8 | Purge de cache, suppressions de base de données, force pushes, suppressions de fichiers |
| Dérive de tâche | 4 | L’agent travaille sur le mauvais problème, explosion de périmètre, terriers de sous-tâches |
| Qualité de sortie | 6 | Vérification fantôme, hésitations sans preuves, rapports incomplets |
| Épuisement des ressources | 3 | Trop de sous-agents créés, boucles sans limite, débordement de contexte |
| Contamination inter-projets | 4 | Un agent dans le projet A modifie des fichiers dans le projet B |
Les 47 hooks restants sont spécifiques à un projet (application de conventions, gardes de déploiement, validateurs de traduction) ou expérimentaux (suivi des coûts, métriques de session, battements d’activité).
Les six catégories structurelles sont stables. Les nouveaux incidents au sein de ces catégories sont attrapés par les hooks existants. Les nouvelles catégories sont rares. En six mois d’exploitation, une seule nouvelle catégorie structurelle a émergé (contamination inter-projets, découverte lorsqu’une session s’exécutant dans le projet obsidian-signals a tenté de modifier des fichiers dans blakecrosley.com). Les cinq autres catégories ont été établies au cours des 60 premières sessions.
L’étude Agents of Chaos, une expérience multi-universitaire de 14 jours donnant à six agents d’IA accès à l’e-mail, à bash, aux systèmes de fichiers et à GitHub, a indépendamment identifié des catégories de défaillance qui se chevauchent : réponse disproportionnée (opérations destructives), détournement d’identité (exposition d’identifiants), boucles infinies (épuisement des ressources) et conformité graduelle sous pression (dérive de tâche).[^5] La convergence entre leurs recherches contrôlées et mon expérience en production suggère que ces catégories sont des propriétés structurelles des agents autonomes, et non des artefacts d’une configuration spécifique.
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 conduit à 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) avec un contenu correct (code syntaxiquement valide) qui viole la tâche (mauvaise fonction). Aucun hook n’attrape cela car 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é un contexte important sur le mauvais travail.
Les hooks ne peuvent pas non plus attraper les défaillances de composition où chaque action individuelle est autorisée mais où la séquence produit un résultat non autorisé. 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 pendant une enquête) était nuisible. La garde MCP attrape désormais cette combinaison spécifique, mais les nouvelles compositions restent non couvertes.
L’écart de composition de la chaîne d’approvisionnement opère au même niveau : des composants de confiance se composent en un comportement non autorisé. Les hooks sont des gardes au niveau des composants. Le raisonnement au niveau de la composition requiert un mécanisme différent, qui évalue des séquences d’actions plutôt que des actions individuelles. 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 originale, non la sécurité de la séquence d’actions composée.
L’écart entre les hooks et une sécurité complète est l’écart entre la mémoire institutionnelle et la prévoyance institutionnelle. Les hooks se souviennent de ce qui s’est mal passé. Ils ne prédisent pas ce qui se passera mal ensuite.
Pourquoi le réactif est honnête
Je pourrais concevoir un système de hooks proactif. Énumérer chaque mode de défaillance possible. Écrire des contrôles préventifs pour chacun. Construire une architecture de sécurité complète avant la première session.
Je ne le fais pas parce que la conception proactive exige 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 (manquant le motif réel de défaillance). Le taux de faux positifs éroderait la confiance dans le système de hooks, et je commencerais à ignorer les alertes.
Les hooks réactifs sont honnêtes. Chacun dit : « cette chose spécifique est arrivée, et voici la garde spécifique qui l’empêche ». La garde est précisément calibrée sur la défaillance parce que c’est la défaillance qui a défini la garde. Les faux positifs sont matériellement plus faibles car le motif est extrait d’un incident réel, pas imaginé à partir d’un modèle de menace. Une garde réactive peut toujours correspondre trop largement plus tard à mesure que la base de code évolue, mais la 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 a été livrée. La dérive a consommé du contexte. Chaque première défaillance est le prix d’entrée pour une garde précise et peu bruyante qui empêche la seconde défaillance.
Après plus de 500 sessions, la plupart des catégories structurelles de défaillance ont été rencontrées. Le coût de la première défaillance est amorti sur des centaines de sessions où le hook a empêché la récurrence. Le système devient plus robuste à chaque incident. Pas plus intelligent. Plus robuste.
Chaque hook est une cicatrice. Chaque cicatrice est une leçon. Les leçons se composent.[^2]
FAQ
Puis-je voir vos configurations de hooks ?
Je décris le système de hooks dans mon commentaire au NIST sur la sécurité des agents et le référence tout au long de la série AI Engineering. Les hooks s’enregistrent dans ~/.claude/settings.json et sont dispatchés par type d’événement via ~/.claude/hooks/dispatchers/.
Comment les hooks affectent-ils les performances de l’agent ?
Chaque hook ajoute des millisecondes par appel d’outil. Avec 84 hooks, la surcharge totale est de 200 à 400 ms par appel d’outil selon les hooks qui se déclenchent. Cette surcharge est négligeable comparée au 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 codage IA ?
Les hooks sont spécifiques à Claude Code (modèle d’événements PreToolUse, PostToolUse). Le concept s’applique à tout framework d’agent avec support de middleware ou de plugins. Les implémentations spécifiques ne sont pas portables, mais la taxonomie des cicatrices et la méthodologie réactive s’appliquent universellement.
Que se passe-t-il lorsqu’un hook bloque une action ?
Les blocages stricts (exit 2) empêchent l’action et injectent un message expliquant pourquoi. L’agent voit la raison du blocage et s’ajuste. Les hooks consultatifs (exit 0) journalisent la préoccupation mais autorisent l’action. Les opérations destructives utilisent des blocages stricts. La plupart des autres catégories utilisent des hooks consultatifs. Le verrou par phrase de passe n’est utilisé que pour les opérations les plus dangereuses (purge de cache, suppression d’infrastructure).
Comment décidez-vous entre blocage strict et consultatif ?
Deux classes obtiennent des blocages stricts : les opérations destructives (purges de cache, suppressions de base de données, force pushes, modifications d’infrastructure) et l’exposition d’identifiants (lecture de fichiers de secrets, accès aux magasins de clés). Tout le reste reçoit une journalisation consultative. La distinction repose sur la sévérité de la conséquence : si l’action peut être annulée à moindre coût et ne divulgue pas de secrets, un avis consultatif suffit. Si l’action est irréversible ou expose des identifiants, un blocage strict est nécessaire.
Sources
[^1] : Blake Crosley, « What I Told NIST About AI Agent Security », 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 de cycle de vie Claude Code (v2.1.116), méthodologie de détection de dérive.
[^2] : Blake Crosley, « Compound Context: Why AI Projects Get Better the Longer You Stay With Them », blakecrosley.com, mars 2026. Cadre de composition du contexte : les hooks en tant que l’une des six catégories qui accumulent des retours.
[^3] : Blake Crosley, « The Supply Chain Is the Attack Surface », blakecrosley.com, mars 2026. Écart de composition : des composants autorisés individuellement produisant des résultats non autorisés.
[^4] : Blake Crosley, « Deploy and Defend: The Agent Trust Paradox », blakecrosley.com, mars 2026. Incident de purge de cache et réponse de la garde API destructive.
[^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 d’IA, 10 vulnérabilités de sécurité identifiées, notamment la réponse disproportionnée, le détournement d’identité et les boucles infinies.