Tutoriel · Agents IA

Hooks : des garde-fous déterministes pour un agent IA

Bloquer une commande dangereuse, lancer le linter après chaque écriture, injecter du contexte : les hooks appliquent une règle à coup sûr, là où une consigne peut être oubliée.

Consigne ou code

Écrire « ne fais jamais de git push --force » dans les instructions d’un agent fonctionne presque toujours. Presque : sur une longue session, avec un contexte chargé, une consigne peut être oubliée ou mal interprétée.

Un hook est un script que le harness exécute lui-même à des moments précis du cycle de l’agent. Il ne dépend pas du modèle : s’il refuse une commande, elle n’est pas lancée. Les consignes guident, les hooks garantissent.

Les événements

Les noms ci-dessous sont ceux de Claude Code ; les autres agents proposent des points d’accroche comparables.

ÉvénementMomentUsage typique
PreToolUseAvant l’exécution d’un outilBloquer ou faire confirmer une commande
PostToolUseAprès un outil réussiFormater, lancer un linter, journaliser
UserPromptSubmitÀ l’envoi d’une demandeAjouter du contexte (branche, ticket en cours)
SessionStartAu début d’une sessionCharger un état du projet
StopQuand l’agent pense avoir finiVérifier que les tests passent avant de s’arrêter

Déclarer un hook

Les hooks se déclarent dans .claude/settings.json, versionné avec le projet. Le matcher filtre les outils concernés (une expression régulière sur le nom de l’outil).

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          { "type": "command", "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/garde-commandes.sh" }
        ]
      }
    ],
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          { "type": "command", "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/lint-fichier.sh" }
        ]
      }
    ]
  }
}

Le script reçoit sur son entrée standard un JSON qui décrit l’appel :

{
  "session_id": "abc123",
  "cwd": "/home/dev/acme-crm",
  "hook_event_name": "PreToolUse",
  "tool_name": "Bash",
  "tool_input": { "command": "git push --force origin main" }
}

Son code de sortie décide de la suite : 0 laisse passer, 2 bloque et renvoie le message d’erreur au modèle, un autre code signale une erreur du hook sans bloquer.

Bloquer une commande dangereuse

#!/usr/bin/env bash
# PreToolUse / Bash : refuse les commandes irréversibles ou réservées à un humain.
set -euo pipefail

command=$(jq -r '.tool_input.command // ""')

block() {
  # Code 2 : l'appel est annulé et ce message est renvoyé au modèle, qui peut s'adapter.
  echo "Commande refusée par le garde-fou : $1" >&2
  exit 2
}

case "$command" in
  *"git push"*"--force"*|*"git push"*" -f"*)
    block "pas de push forcé ; proposer un push normal ou demander à l'utilisateur." ;;
  *"rm -rf /"*|*"rm -rf ~"*)
    block "suppression récursive hors du projet." ;;
  *"sf project deploy"*"--target-org prod"*)
    block "le déploiement en production est réservé à l'utilisateur." ;;
esac

exit 0

Le message d’erreur s’adresse au modèle : il doit dire quoi faire à la place. Un refus sec pousse l’agent à contourner ; une alternative claire le remet sur la bonne voie.

Ce filtrage par motif reste un garde-fou, pas une barrière de sécurité complète : une commande peut être écrite de mille façons. Pour une protection forte, le combiner avec des droits limités (pas d’accès à l’org de production depuis le poste de l’agent, par exemple).

Lancer le linter après chaque écriture

#!/usr/bin/env bash
# PostToolUse / Edit|Write : vérifie le fichier qui vient d'être écrit.
set -uo pipefail

file=$(jq -r '.tool_input.file_path // ""')

case "$file" in
  *.php) output=$(php -l "$file" 2>&1) ;;
  *.js|*.ts) output=$(npx eslint "$file" 2>&1) ;;
  *) exit 0 ;;
esac

if [ $? -ne 0 ]; then
  # Après l'outil, le code 2 ne peut plus annuler l'écriture : il renvoie l'erreur au modèle pour correction.
  echo "$output" >&2
  exit 2
fi

L’agent reçoit l’erreur de syntaxe immédiatement, sur le fichier qu’il vient de modifier, et la corrige dans la foulée au lieu de la découvrir au build.

Bonnes pratiques

  • Rapides : un hook s’exécute à chaque appel d’outil. Viser quelques centaines de millisecondes ; réserver les suites de tests au hook Stop.
  • Ciblés : un matcher précis, et une sortie immédiate pour les cas non concernés.
  • Testés : un hook se teste comme un script, en lui passant un JSON d’exemple sur l’entrée standard.
  • Relus : un hook exécute du code avec vos droits. Ne jamais installer un hook dont on n’a pas lu le contenu.
  • Documentés : la règle appliquée par le hook figure aussi dans les instructions, pour que l’agent la respecte d’emblée plutôt que de se faire bloquer.
🍪 Gestion des cookies
Ce site utilise des cookies pour mesurer l'audience (Google Analytics) et lire des vidéos (YouTube). Vous pouvez personnaliser votre choix.
Mentions légales
Réglages des cookies
Essentiels Nécessaires au fonctionnement du site — ne peuvent pas être désactivés.
Google Analytics Analyse d'audience (pages vues, sources de trafic). Aucune donnée personnelle identifiante.
YouTube Permet de lire les vidéos YouTube intégrées sur cette page.