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énement | Moment | Usage typique |
|---|---|---|
PreToolUse | Avant l’exécution d’un outil | Bloquer ou faire confirmer une commande |
PostToolUse | Après un outil réussi | Formater, lancer un linter, journaliser |
UserPromptSubmit | À l’envoi d’une demande | Ajouter du contexte (branche, ticket en cours) |
SessionStart | Au début d’une session | Charger un état du projet |
Stop | Quand l’agent pense avoir fini | Vé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
matcherpré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.