Tech & Performance
Hooks Claude Code : garde-fous pour comptes Ads
Hooks Claude Code : un script PreToolUse refuse tout outil d’un serveur MCP publicitaire hors liste de lecture, un journal des appels, et ce qui les contourne.
Une consigne écrite dans un CLAUDE.md est une demande que Claude lit et cherche à suivre. La documentation de Claude Code range ces fichiers dans le contexte, et pour bloquer une action quelle que soit la décision de Claude, elle renvoie vers un hook PreToolUse. Sur un compte publicitaire, c'est la différence entre « ne touche pas aux budgets » écrit dans un fichier et un appel d'outil refusé avant son exécution.
Ce guide montre comment poser deux hooks autour d'un serveur MCP publicitaire, une liste d'outils autorisés en lecture et un journal des appels, puis passe en revue ce qui les fait sauter. Les faits viennent de la documentation de Claude Code (guide et référence des hooks, mémoire, MCP, sous-agents) et des pages de référence du serveur MCP publicités de Meta, consultées le 14 octobre 2026. Le script et la configuration d'exemple sont notre assemblage de ces pages : aucune n'en publie de semblables.
Ce qu'est un hook
D'après le guide des hooks, un hook est une commande définie par l'utilisateur que Claude Code exécute à un point précis de son cycle de vie, ce qui donne un contrôle déterministe : certaines actions ont lieu selon la configuration, sans dépendre d'un choix du modèle. La référence des hooks décrit une configuration en trois niveaux : un événement (par exemple PreToolUse, avant un appel d'outil, ou PostToolUse, après), un filtre appelé matcher, et un ou plusieurs gestionnaires. Les gestionnaires peuvent être une commande shell, une requête HTTP, un appel d'outil MCP, une consigne envoyée à un modèle ou un sous-agent ; ce dernier type est présenté comme expérimental.
L'endroit où l'on écrit le hook fixe sa portée :
| Emplacement | Portée | Partage |
|---|---|---|
~/.claude/settings.json | Tous vos projets | Non, local à votre machine |
.claude/settings.json | Un projet | Oui, versionnable dans le dépôt |
.claude/settings.local.json | Un projet | Non |
| Réglages gérés (managed policy) | Toute l'organisation | Oui, contrôlés par un administrateur |
Plugin, fichier hooks/hooks.json | Quand le plugin est activé | Oui, livrés avec le plugin |
La commande /hooks ouvre dans Claude Code un navigateur en lecture seule des hooks configurés, avec leur provenance : le guide en fait l'étape de vérification après l'ajout d'un hook.
Cibler les outils d'un serveur MCP publicitaire
Les outils d'un serveur MCP apparaissent dans les événements d'outils comme n'importe quel autre outil, sous la forme mcp__<serveur>__<outil>, où le serveur est le nom du serveur MCP tel qu'il est configuré. Pour un serveur Meta Ads nommé meta-ads, le matcher qui couvre tous ses outils est mcp__meta-ads__.*. La documentation insiste sur le .* final : un matcher comme mcp__meta-ads ne contient que des caractères évalués comme une chaîne exacte, et ne correspond à aucun outil. Les matchers distinguent les majuscules des minuscules, et un matcher qui contient d'autres caractères est lu comme une expression régulière JavaScript, non ancrée.
Pour décider quels outils autoriser, il faut leurs descriptions. Les pages de référence du reporting complet et de la création et gestion des publicités de Meta donnent, par exemple, ces descriptions : ads_get_ad_entities récupère les campagnes, ensembles et annonces avec leurs indicateurs de performance ; ads_insights_performance_trend suit l'évolution d'indicateurs dans le temps ; ads_insights_anomaly_signal détecte des écarts de performance ; ads_update_entity modifie les champs d'une campagne, d'une publicité ou d'un ensemble existant ; ads_activate_entity active un élément mis en pause, ce qui déclenche les dépenses budgétaires. Le détail de chaque outil et nos règles de permission figurent dans notre guide du MCP Meta Ads.
Premier garde-fou : une liste d'outils autorisés
Le principe : tout outil du serveur absent d'un fichier de lecture est refusé. Le fichier .claude/hooks/outils-lecture.txt liste, un par ligne, les outils que vous avez lus dans la documentation et jugés sans effet sur le compte :
mcp__meta-ads__ads_get_ad_entities
mcp__meta-ads__ads_insights_performance_trend
mcp__meta-ads__ads_insights_anomaly_signalLe script .claude/hooks/lecture-seule.sh lit le nom de l'outil sur l'entrée standard :
#!/bin/bash
OUTIL=$(jq -r '.tool_name // empty')
LISTE="$(dirname "$0")/outils-lecture.txt"
if [ -n "$OUTIL" ] && grep -qxF "$OUTIL" "$LISTE" 2>/dev/null; then
exit 0
fi
echo "Bloqué : $OUTIL n'est pas dans outils-lecture.txt (lecture seule sur ce compte)." >&2
exit 2La sortie 2 bloque l'appel et le message écrit sur la sortie d'erreur parvient à Claude comme motif du refus. La sortie 0 signifie seulement que le hook ne s'oppose pas : pour un hook PreToolUse, elle n'approuve pas l'appel, et le flux normal des permissions s'applique ensuite. Sans jq sur la machine, la variable reste vide et le script refuse tout : c'est le comportement voulu pour un garde-fou. On le déclare dans .claude/settings.json :
{
"hooks": {
"PreToolUse": [
{
"matcher": "mcp__meta-ads__.*",
"hooks": [
{
"type": "command",
"command": "jq -c '{ts: (now | todate), session: .session_id, outil: .tool_name}' >> ~/.claude/journal-ads.jsonl"
},
{
"type": "command",
"command": "bash",
"args": ["${CLAUDE_PROJECT_DIR}/.claude/hooks/lecture-seule.sh"],
"onFailure": "block"
}
]
}
]
}
}Le premier gestionnaire est le journal, détaillé plus bas. Le second est le garde-fou, en forme « exec » : avec le champ args, Claude Code lance l'exécutable directement, sans shell, et chaque élément est passé comme un seul argument. Le champ onFailure est expliqué dans la section sur les contournements.
Pour le tester hors session, la documentation conseille d'envoyer un exemple d'entrée au script et de lire le code de sortie :
echo '{"tool_name":"mcp__meta-ads__ads_activate_entity"}' | bash .claude/hooks/lecture-seule.sh; echo $?
echo '{"tool_name":"mcp__meta-ads__ads_get_ad_entities"}' | bash .claude/hooks/lecture-seule.sh; echo $?Le premier appel affiche le message de blocage puis 2, le second affiche 0. Pour vérifier le branchement dans une session sans toucher au compte, retirez temporairement un outil de lecture du fichier de liste et demandez à Claude une lecture : elle doit être refusée, avec votre message.
Second garde-fou : un journal des appels
Le premier gestionnaire de l'exemple écrit une ligne JSON par appel d'outil du serveur : l'horodatage, l'identifiant de session et le nom de l'outil. Nous n'y mettons pas les arguments, qui peuvent contenir des données du client. La documentation décrit ce montage : quand plusieurs hooks correspondent au même événement, ils s'exécutent tous, en parallèle, avant que Claude Code fusionne les résultats. Si l'un refuse l'appel, les autres s'exécutent quand même, donc le journal enregistre aussi les appels que le garde-fou bloque. Pour les décisions d'un hook PreToolUse, la réponse la plus restrictive l'emporte, dans l'ordre deny, defer, ask, allow.
Le journal lui-même a une limite : il trace les appels d'outils vus par Claude Code, et rien d'autre. Il ne dit pas ce qu'un appel a modifié côté plateforme. À notre avis, le journal d'activité du compte reste la référence pour cela ; Meta documente dans son serveur MCP l'outil ads_account_get_activity_logs, qui récupère les entrées de l'historique d'activité d'un compte publicitaire.
Ce qui fait sauter un garde-fou
C'est, à notre avis, la partie la plus utile de la documentation, parce que les défauts vont dans le sens de l'action qui a lieu.
- Le code de sortie 1 ne bloque pas. Sans JSON valide sur la sortie standard, Claude Code traite le code 1 comme une erreur non bloquante, bien que 1 soit le code d'échec habituel sous Unix. La documentation l'écrit à propos des politiques : utilisez
exit 2. - Un script introuvable ou non exécutable est une erreur non bloquante. En forme shell, le shell sort avec un code comme 127 et l'action a lieu : une faute de frappe dans le chemin du fichier de réglages et le hook ne s'exécute pas. En forme exec, la documentation donne l'exemple d'un script manquant dont l'appel s'exécute quand même tant que
onFailuren'est pas défini. Elle conseille de surveiller l'avis « hook error » au premier lancement d'un hook de politique. - Un délai dépassé ne bloque pas non plus. Pour un hook
PreToolUsede typecommand,httpoumcp_tool, l'appel poursuit son chemin dans le flux normal des permissions : un hook qui reste sans réponse ne fait pas office de verrou. - Le remède des deux cas précédents est
onFailure: "block"sur un hookcommandouhttp, d'où sa présence dans notre exemple. Il demande Claude Code v2.1.295 ou plus ; en dessous, vérifiez la version avant de compter dessus. - Un matcher trop court ne correspond à rien. Le cas
mcp__meta-adssans.*a été vu plus haut. Si le serveur est fourni par un plugin, ses outils s'appellentmcp__plugin_<plugin>_<serveur>__<outil>et un matcher écrit avec le nom nu du serveur ne se déclenche pas. - Un hook sur les outils MCP ne voit pas le shell. Claude peut aussi modifier des fichiers et lancer des commandes par l'outil Bash. La documentation précise que le filtre
ifdes hooks est approximatif et recommande le système de permissions pour un refus strict. À notre avis, associez donc le hook à des règlesdenysur les commandes qui atteindraient l'API du compte. - Un hook
PostToolUsearrive trop tard : l'outil s'est déjà exécuté et le hook ne peut rien annuler.
Ce qui tient, et pour qui
Un hook PreToolUse s'exécute avant la vérification du mode de permission, dans tous les modes. D'après la documentation, un refus renvoyé par un hook bloque l'outil même en mode bypassPermissions ou avec --dangerously-skip-permissions : on peut donc imposer une règle qu'un utilisateur ne contourne pas en changeant de mode. Un mod installé qui gère tool.check fait exception : il répond après les hooks et peut approuver un appel qu'un hook PreToolUse a bloqué, sauf si ce hook est dans les réglages gérés. La réciproque est fausse : un hook qui répond allow n'efface ni les règles deny des réglages, ni les invites que Claude Code garde pour certains outils.
Deux points concernent les agences qui travaillent sur les dépôts de leurs clients. Dans une session interactive, Claude Code retient les hooks de tous les fichiers de réglages, y compris les vôtres, jusqu'à ce que vous acceptiez la boîte de dialogue de confiance du dossier. Dans une session -p ou SDK, la boîte de dialogue n'apparaît pas et le dossier est traité comme fiable : les hooks versionnés dans le .claude/settings.json d'un dépôt s'exécutent dans un dossier que vous n'avez pas approuvé. La documentation conseille de relire le dossier .claude/ d'un dépôt tiers avant d'y lancer claude -p, ou de lancer avec --bare ou avec --settings '{"disableAllHooks": true}'.
Enfin, les hooks de type commande s'exécutent avec toutes les permissions de votre compte utilisateur, et la documentation demande de relire et tester chaque commande avant de l'ajouter, et recommande de valider les entrées, de mettre les variables shell entre guillemets et d'utiliser des chemins absolus. Pour une règle d'agence qui doit valoir pour tous, les réglages gérés permettent à un administrateur de poser des hooks que le paramètre disableAllHooks des réglages utilisateur, projet ou local ne désactive pas.
Aller plus loin
Deux autres types de gestionnaires servent d'autres besoins. Les hooks de type prompt envoient la décision à un modèle quand la règle demande du jugement plutôt qu'un test déterministe ; la documentation les propose pour ce cas. Les hooks de type http envoient les événements à un service, par exemple un service d'audit commun à une équipe ; une réponse hors 2xx est une erreur non bloquante, sauf avec onFailure. Les hooks s'appliquent aussi aux sous-agents qui héritent des outils du serveur ; les auditeurs en lecture seule de notre guide des sous-agents n'ont aucun outil MCP.
Par où commencer
- Relever les noms complets des outils dans
/mcp, qui affiche le nom complet d'un outil sélectionné, et lire la description de chacun dans la documentation de la plateforme. - Écrire
outils-lecture.txtavec les seuls outils sans effet sur le compte. - Tester le script avec un exemple d'entrée, pour les deux codes de sortie.
- Déclarer le hook avec
onFailureet le journal, puis vérifier avec/hooks. - Doubler le tout de règles
denysur les outils d'écriture et sur les commandes shell qui atteignent l'API.
Questions fréquentes sur les hooks de Claude Code
Un hook remplace-t-il les règles de permission ?
Non, les deux se complètent. D’après la documentation de Claude Code, les règles deny et ask restent évaluées quelle que soit la réponse d’un hook, et un hook qui renvoie allow ne passe pas outre une règle deny des réglages. La documentation recommande aussi d’utiliser le système de permissions plutôt qu’un hook pour imposer une autorisation ou un refus strict sur des commandes Bash, car le filtre du champ if est approximatif. Pour un serveur MCP publicitaire, nos règles ask et deny sont décrites dans le guide du MCP Meta Ads.
Un hook peut-il annuler une action déjà exécutée ?
Non. La documentation précise qu’un hook PostToolUse ne peut pas annuler une action, puisque l’outil s’est déjà exécuté. Pour empêcher une écriture sur un compte, un hook PreToolUse convient : il s’exécute avant l’appel et peut le bloquer.
Les hooks s’appliquent-ils aussi aux sous-agents ?
Oui. D’après la documentation, les hooks des fichiers de réglages, des réglages gérés et des plugins s’exécutent aussi dans les sous-agents : quand un sous-agent appelle un outil, les événements PreToolUse et PostToolUse déclenchent les mêmes hooks que dans la conversation principale, avec les champs agent_id et agent_type dans l’entrée. Un sous-agent qui hérite des outils MCP est donc soumis au même garde-fou. Les auditeurs de notre guide des sous-agents, dont le champ tools ne liste que Read, Grep et Glob, n’ont pas d’outil MCP : le garde-fou du serveur ne les concerne pas.
Que se passe-t-il si mon script de garde-fou plante ou reste introuvable ?
Par défaut, l’action a lieu. La documentation classe un script introuvable, non exécutable ou qui sort avec un code autre que 0 et 2 parmi les erreurs non bloquantes, et un hook qui dépasse son délai ne bloque pas non plus l’appel. Le champ onFailure avec la valeur block, disponible à partir de Claude Code v2.1.295, inverse ce comportement pour les hooks de type command et http.