Ça a commencé par une observation qui m'agaçait. Je mets dans mes fichiers d'instructions une règle simple — utiliser AskUserQuestion quand la compréhension n'est pas sûre — et régulièrement, elle est oubliée. Pas contournée volontairement : oubliée en cours de route, comme si la conversation avançait plus vite que la mémoire de la règle.
En creusant, j'ai vu que d'autres personnes qui travaillent avec des agents construisent des "hooks" — des scripts qui s'exécutent à des moments précis du fonctionnement de l'agent. Ma première question était un peu bête, mais utile : c'est encore une contrainte de plus à écrire quelque part, ou c'est autre chose que des instructions ?
C'est autre chose, et comprendre pourquoi m'a fait revoir une intuition que j'avais depuis un moment. Je pensais qu'un fichier d'instructions bien écrit, avec les bons champs de permissions, suffisait à garantir un comportement. Ce n'est pas vrai. Tout ce qui passe par le contexte d'un agent — un CLAUDE.md, une liste d'outils autorisés donnée à un sous-agent — reste, au fond, une suggestion. Rien ne garantit qu'elle sera honorée à un tour donné, et plus la conversation s'allonge, plus l'attention du modèle dérive des instructions du début.
Les hooks, eux, ne passent pas par le modèle. Ce sont des commandes shell exécutées hors de lui, à des points fixes de son cycle de vie : avant qu'il appelle un outil, après, quand il reçoit un message, quand il termine sa réponse. Ils reçoivent du JSON en entrée — quel outil, quels paramètres — et répondent par un code de sortie. Code 2 : ça bloque, même si le modèle "voulait" continuer. C'est la première fois que je vois une couche qui ne demande pas l'avis du modèle pour agir.
J'ai voulu vérifier si cette logique tenait aussi pour les
sous-agents, parce que j'en construis pour séparer des tâches — un
agent RH, un agent sécurité, un agent SEO sur ce site. Ma question :
si je restreins la liste d'outils d'un sous-agent — pas d'Edit,
seulement Read et Grep — est-ce que ça garantit qu'il ne peut pas
écrire ? Réponse : non. Deux bugs ouverts et documentés sur le dépôt
officiel de Claude Code montrent des sous-agents contournant les
règles de permission alors même que l'outil concerné n'était pas dans
leur liste autorisée. Et même sans bug : un sous-agent qui a accès à
Bash peut modifier un fichier avec un simple sed -i, sans
jamais appeler l'outil "Edit" nommé comme tel. La restriction sur les
outils nommés ne couvre pas ce qu'on peut faire à côté.
Ça m'a forcé à revoir ma façon de penser la sécurité de mes propres architectures d'agents. La vraie garantie, ce n'est pas un champ d'outils autorisés bien rempli, c'est un hook qui s'exécute avant le système de permission et qui peut bloquer un appel, quel que soit l'agent qui l'a fait, quel que soit le chemin pris pour y arriver. Une image qui m'a parlé, lue dans une discussion sur le sujet : une porte sans poignée, plutôt qu'une porte avec une pancarte "ne pas ouvrir".
tools: restreint (ici Read/Grep, sans Edit) reste une
restriction déclarée, pas une garantie : deux bugs documentés sur
le dépôt officiel (issues #25000 et SDK #115, voir Références) et un simple
détour par Bash suffisent à l'atteindre quand même. Seule la barrière du
bas — un hook PreToolUse, hors du modèle — intercepte les trois chemins en
même temps, quel que soit celui emprunté.
Ce que je n'avais pas vu non plus : un hook n'a aucun accès à l'état interne du modèle. Il ne voit que des événements observables de l'extérieur. Ma règle de départ — demander si l'agent n'est pas sûr — n'est pas hookable telle quelle, parce que "le modèle n'était pas sûr" n'est pas une donnée qu'un hook peut lire. Ce n'est pas que je m'y prends mal, c'est hors de portée par construction. Ce qui devient hookable, c'est la version reformulée en condition observable : pas "demande si incertain", mais "bloque toute écriture tant qu'aucun appel à AskUserQuestion n'a eu lieu dans cette tâche". Présence ou absence d'un événement, ça, un hook sait le vérifier.
Autre angle mort découvert en creusant : je pensais qu'un agent principal "reprenait la main" si la réponse d'un sous-agent ne lui convenait pas. Faux, par défaut. Un sous-agent tourne dans son propre contexte isolé et ne retourne qu'une chaîne de résultat final au parent. Rien ne vérifie automatiquement que ce résultat est bon. Si je veux qu'une mauvaise réponse soit rattrapée, il faut construire l'étape de validation moi-même — ce n'est pas un réflexe intégré à l'outil, c'est un choix d'architecture à faire.
Ça éclaire aussi un truc qui m'agaçait sans que j'en comprenne la cause : chez le client, avec Copilot, même en sélectionnant Sonnet comme modèle, ça part parfois n'importe comment. Copilot n'a pas d'équivalent officiel de premier ordre pour les hooks — je n'ai pas trouvé de trace d'une couche déterministe comparable dans ses réglages. Ça veut dire que la seule main que j'y ai, c'est la couche instructions, probabiliste. Le modèle est le même que celui que j'utilise ailleurs, mais le harnais autour n'offre pas la même prise. Ça confirme une distinction que j'avais un peu ratée : le modèle et le harnais sont deux choses séparées, et une bonne partie de la fiabilité perçue d'un outil se joue dans le harnais, pas dans le modèle branché dessus.
| Couche | Claude Code | Copilot |
|---|---|---|
| Instructions générales | CLAUDE.md (racine + global) | copilot-instructions.md ; AGENTS.md aussi lu |
| Instructions par chemin | CLAUDE.md imbriqués | fichiers *.instructions.md + applyTo |
| Permissions / comportement | settings.json (projet, perso, global) | pas d'équivalent officiel de même maturité |
| Hooks (déterministe) | clé hooks dans settings.json |
rien d'officiel de premier ordre trouvé |
| Agents spécialisés | fichiers d'agents dédiés | pas de format perso officiel ; Copilot Studio côté entreprise |
| Skills | SKILL.md par dossier | "prompt files", moins standardisé |
| MCP | fichier de config projet ou perso | supporté aussi |
Je ne tire pas de tout ça une promesse de contrôle total. Un hook n'a de prise que sur ce qui est observable et formulable en condition binaire — tout ce qui reste un jugement pur, "est-ce que c'était vraiment ambigu", aucun hook ne le fait à ma place. Ce n'est pas un trou dans les hooks, c'est la frontière naturelle entre ce qu'une couche déterministe peut garantir et ce qui reste mon rôle de relecture. Mais je sais maintenant où est cette frontière — et je ne demande plus poliment à un prompt de faire le travail d'une porte fermée à clé.
Références
- Claude Code — documentation des hooks
- Claude Code — documentation des settings (clé
hooks) - Claude Agent SDK — documentation des permissions
- Claude — annonce des sous-agents dans Claude Code
- Claude Code — issue #25000 : sous-agents contournant les règles de permission
- Claude Agent SDK (TypeScript) — issue #115 : Edit utilisé malgré une liste d'outils restreinte
- Nicolas Bustamante — sur la distinction modèle / harnais