Hooks
Les hooks intègrent des commandes shell arbitraires au cycle de vie de la CLI Vibe Code pour filtrer, auditer ou réécrire le comportement de l'agent. Utilisez-les pour bloquer des appels d'outils dangereux, masquer des arguments avant l'exécution d'un outil, ajouter du contexte après le retour d'un outil ou forcer l'agent à reprendre une réponse insatisfaisante.
Déclarez-les dans un fichier hooks.toml et ils prennent effet au prochain lancement.
Emplacements des fichiers de configuration
Les hooks sont déclarés dans hooks.toml. La CLI les recherche dans cet ordre :
./.vibe/hooks.tomldans le répertoire de travail (niveau projet, chargé en premier, uniquement pour les dossiers de confiance).~/.vibe/hooks.tomldans votre répertoire personnel (niveau utilisateur, chargé en second).
Quand un même name apparaît dans les deux fichiers, l'entrée du projet l'emporte. Les hooks de niveau projet ne sont chargés que lorsque le répertoire de travail est de confiance.
Les sous-agents héritent de la configuration des hooks du parent, de sorte que les politiques s'appliquent de façon transitive.
Hooks des plugins
Un plugin peut déclarer des hooks dans son fichier ai.mistral.vibe/hooks.toml, avec les mêmes champs que hooks.toml. Vibe préfixe chacun d'eux avec le nom du plugin, par exemple release-tools:audit-bash, et l'affiche dans /plugins.
Dans la version actuelle, Vibe n'exécute pas les hooks des plugins. Pour appliquer un hook, déclarez-le dans ./.vibe/hooks.toml ou ~/.vibe/hooks.toml.
Déclarer un hook
Chaque hook est une table [[hooks]] :
[[hooks]]
name = "deny-rm-rf"
type = "pre_tool"
match = "bash"
command = "uv run python /path/to/guard-bash"
timeout = 60.0
strict = false
description = "Reject dangerous shell commands."| Champ | Requis | Détails |
|---|---|---|
name | oui | Identifiant unique. Utilisé pour dédupliquer entre les fichiers projet et utilisateur. |
type | oui | Une valeur parmi pre_tool, post_tool, post_agent. |
command | oui | Commande shell à exécuter. Reçoit la charge utile du hook sur stdin. Sous Windows, s'exécute dans Git Bash s'il est disponible, sinon dans PowerShell, comme l'outil bash. |
match | pre_tool / post_tool | Filtre sur le nom de l'outil : glob fnmatch, ou regex avec le préfixe re:. Insensible à la casse. |
timeout | non | Secondes avant que le hook ne soit interrompu. Défaut : 60. |
strict | hooks d'outil uniquement | Si true, les échecs de parsing et d'exécution deviennent des refus (pre_tool) ou des effacements de texte (post_tool) au lieu d'avertissements. Défaut : false. |
description | non | Texte libre affiché dans les diagnostics. |
Contrat du hook
Chaque hook est invoqué avec un objet JSON UTF-8 sur stdin contenant le contexte de session : session_id, parent_session_id, transcript_path, cwd et hook_event_name. Les hooks d'outil ajoutent des champs spécifiques (voir ci-dessous).
Les hooks répondent via leur code de sortie et leur stdout. Utilisez stderr pour les diagnostics et les logs de debug.
| Code de sortie / stdout | Effet |
|---|---|
Code 0, stdout vide | Passthrough. |
Code 0, objet JSON valide sur stdout | Réponse structurée (voir les champs ci-dessous). |
Code 0, stdout non vide mais non conforme | Échec du hook. Avertissement par défaut ; refus / effacement sous strict = true sur un hook d'outil. |
| Code non nul, timeout ou échec de spawn | Échec du hook. Diagnostic tiré de stderr (fallback sur stdout, puis le code de sortie). |
Champs universels de premier niveau dans la réponse JSON :
system_message(chaîne, optionnel) — affiché à l'utilisateur dans l'interface.decision("allow"|"deny", optionnel, défaut"allow") — l'effet de"deny"dépend du type de hook.reason(chaîne, optionnel) — accompagnedecision: "deny".hook_specific_output(objet, optionnel) — charge utile spécifique à l'événement.
Les champs JSON inconnus sont tolérés à tous les niveaux (compatibilité ascendante). Les champs qui n'ont pas de sens pour le type de hook courant sont ignorés silencieusement.
pre_toolpre_tool
Déclenché à chaque appel d'outil, avant la demande d'autorisation utilisateur. Le premier refus court-circuite les hooks pre_tool restants pour cet appel.
Reçoit (en plus du contexte de session) : tool_name, tool_call_id, tool_input (les arguments bruts du modèle).
tool_name est le nom complet de l'outil, comme file_system.bash ou <server>.<tool> pour les outils MCP. match accepte les noms complets comme les noms courts, donc match = "bash" fonctionne. Les commandes que l'agent lance en arrière-plan passent par process_start au lieu de bash, couvrez donc les deux dans vos gardes shell : match = "re:bash|process_start".
Peut renvoyer :
decision: "deny"+reason— refuse l'appel ;reasondevient l'erreur d'outil visible par le LLM.hook_specific_output.tool_input(objet) — remplacement complet des arguments du modèle. Le remplacement est de nouveau validé selon le schéma de l'outil ; un échec de validation devient un refus synthétisé. Les réécritures s'enchaînent de gauche à droite d'un hook à l'autre. Les arguments réécrits sont aussi ceux qu'affiche la demande d'autorisation, ceux avec lesquels l'outil s'exécute et ceux que voient les tours LLM suivants dans le message de l'assistant.system_message— interface uniquement.
post_toolpost_tool
Se déclenche à chaque appel d'outil si et seulement si le corps de l'outil s'est réellement exécuté. Il ne se déclenche pas si l'outil n'a jamais été exécuté : refus par pre_tool, refus au prompt d'approbation ou permission définie sur never.
Reçoit (en plus du contexte de session) :
tool_name,tool_call_idtool_input(post-réécriture)tool_status—successoufailuretool_output— dict de résultat structuré ;nullen cas d'échectool_output_text— le texte courant que le LLM verra, mutable par les hooks précédentstool_errorduration_ms— pas encore remonté (toujours0)
Peut renvoyer :
decision: "deny"+reason— remplacetool_output_textparreason. Le pipeline continue ; les hooks suivants voient le remplacement.hook_specific_output.additional_context(chaîne) — ajouté (avec un séparateur\n) àtool_output_text. Se combine avec un refus du même hook : le refus remplace d'abord, puisadditional_contextest ajouté au remplacement.system_message— interface uniquement.
post_agentpost_agent
Déclenché après chaque tour de l'assistant qui se termine sans appel d'outil en attente.
Reçoit (en plus du contexte de session) : aucun champ supplémentaire.
Peut renvoyer :
decision: "deny"+reason—reasonest injecté comme un nouveau message utilisateur demandant à l'agent de réessayer. Plafonné à 3 tentatives par tour pour l'ensemble des Hookspost_agent; au-delà, Vibe accepte la réponse. Si un Hookpost_agentéchoue, Vibe accepte la réponse et affiche un avertissement.system_message— interface uniquement.
Exemple : bloquer les commandes shell destructrices
./.vibe/hooks.toml :
[[hooks]]
name = "deny-rm-rf"
type = "pre_tool"
match = "re:bash|process_start"
command = "python ./.vibe/hooks/guard-bash.py"
strict = true
description = "Reject rm -rf and other destructive shell commands."./.vibe/hooks/guard-bash.py :
import json
import sys
payload = json.load(sys.stdin)
command = payload.get("tool_input", {}).get("command", "")
if "rm -rf" in command:
print(json.dumps({
"decision": "deny",
"reason": "rm -rf is blocked by the deny-rm-rf hook.",
}))
sys.exit(0)
# Passthrough: empty stdout, exit 0.Avec strict = true, tout crash du script ou toute sortie JSON malformée refuse également l'appel au lieu de se limiter à un avertissement.