Hooks Cursor : le guide complet, et pourquoi ils ne se déclenchent pas toujours
Les hooks de Cursor permettent à un script de surveiller ou de bloquer ce que fait l’agent : chaque commande shell, chaque appel MCP, chaque lecture et modification de fichier. Ils sont puissants, mais ils échouent en silence. Voici tout ce que nous avons appris en les mettant en place pour de vrai.
RÉPONSE COURTE
Placez un hooks.json avec "version": 1 dans ~/.cursor/ (tous les projets) ou dans .cursor/ d’un dépôt, associez chaque événement à une commande et écrivez du JSON sur stdout. Utilisez beforeShellExecution et beforeMCPExecution pour autoriser, refuser ou demander, et définissez failClosed: true si un hook défaillant doit bloquer au lieu de laisser passer la commande. Quand un hook semble ignoré, ouvrez d’abord le canal de sortie Hooks : la plupart des problèmes viennent d’un chemin, d’un espace de travail non approuvé, d’un JSON invalide ou d’un événement que la CLI Cursor n’envoie pas encore.
Ce que sont les hooks Cursor
Un hook est une commande que Cursor exécute à un point précis de la boucle de l’agent. Cursor envoie sur l’entrée standard de la commande un objet JSON décrivant ce qui est sur le point de se produire, attend, puis lit une réponse JSON sur sa sortie standard. Certains hooks se contentent d’observer (journaliser la modification, formater le fichier). D’autres décident : un hook beforeShellExecution peut laisser une commande s’exécuter, l’arrêter ou vous demander d’abord.
Les hooks sont donc l’outil adapté à trois usages : les garde-fous (ne jamais exécuter rm -rf ou git push --force sans un humain), l’automatisation (formater ou analyser le code après chaque modification) et la visibilité (journaliser ou transmettre ce que fait l’agent pour savoir quand il a besoin de vous).
Où se trouve hooks.json
Cursor lit les hooks à quatre emplacements et les exécute tous. Chaque hook correspondant de chaque source s’exécute, et Cursor fusionne leurs réponses.
| Niveau | Fichier | Remarques |
|---|---|---|
| Utilisateur | ~/.cursor/hooks.json | S’applique à tous les projets. Les commandes relatives sont résolues depuis ~/.cursor/. |
| Projet | <project>/.cursor/hooks.json | Versionné dans le dépôt. S’exécute uniquement dans un espace de travail approuvé. Les commandes relatives sont résolues depuis la racine du projet. |
| Équipe | Tableau de bord web de Cursor | Enterprise uniquement, synchronisé pour chaque membre. |
| Entreprise | C:\ProgramData\Cursor\hooks.json (Windows), /Library/Application Support/Cursor/hooks.json (macOS), /etc/cursor/hooks.json (Linux, WSL) | Géré par le service informatique. |
Quand les réponses divergent, la plus stricte l’emporte : un deny de n’importe quelle source l’emporte sur ask, et ask l’emporte sur allow. Le plus petit fichier fonctionnel ressemble à ceci (c’est l’exemple de la documentation de Cursor) :
version doit valoir 1. Cursor surveille le fichier et le recharge à l’enregistrement. Si une modification ne prend pas effet, redémarrez Cursor. Cela suffit à résoudre un nombre étonnant de signalements sur le forum.
Les événements que vous pouvez intercepter
La liste actuelle de la documentation de Cursor, regroupée par usage :
| Groupe | Événements | Peut-il bloquer ? |
|---|---|---|
| Shell et MCP | beforeShellExecution, afterShellExecution, beforeMCPExecution, afterMCPExecution | Les deux hooks before… : autoriser, refuser ou demander |
| Tout outil | preToolUse, postToolUse, postToolUseFailure | preToolUse : autoriser ou refuser (ask n’est pas appliqué aujourd’hui) |
| Fichiers | beforeReadFile, afterFileEdit | beforeReadFile : autoriser ou refuser |
| Prompt | beforeSubmitPrompt | Oui, avec continue: false |
| Boucle de l’agent | stop, afterAgentResponse, afterAgentThought, preCompact | Non (stop peut envoyer un message de suivi) |
| Sous-agents | subagentStart, subagentStop | subagentStart : autoriser ou refuser (ask compte comme un refus) |
| Session et application | sessionStart, sessionEnd, workspaceOpen | Non |
| Complétions Tab | beforeTabFileRead, afterTabFileEdit | beforeTabFileRead : autoriser ou refuser |
Chaque événement reçoit quelques champs communs (conversation_id, generation_id, model, hook_event_name, cursor_version, workspace_roots) en plus des siens. beforeShellExecution reçoit la command complète et son cwd ; stop reçoit un status qui vaut completed, aborted ou error.
Autoriser, refuser et demander
Un hook décisionnel écrit un seul objet JSON. Pour le hook shell, cela ressemble à ceci :
permissionvautallow,denyouask.user_messageest ce que vous voyez dans Cursor.agent_messageest ce qui est communiqué à l’agent, pour qu’il change d’approche au lieu de réessayer la même commande.
Les codes de sortie comptent autant que le JSON. Le code 0 signifie « utilise mon JSON ». Le code 2 bloque l’action, comme deny. Tout autre code de sortie est traité comme un échec du hook, et l’action se poursuit, sauf si vous définissez failClosed. Pour les hooks d’autorisation, un JSON invalide ou non conforme au schéma bloque l’action.
La documentation de Cursor indique que ask est pris en charge par beforeShellExecution et beforeMCPExecution. En mars 2026, l’équipe de Cursor a écrit sur le forum que les hooks n’appliquaient que deny et que allow et ask étaient « basically ignored » (pratiquement ignorés), en particulier avec la liste d’autorisation de commandes ou le bac à sable activés. Nous n’avons trouvé aucune annonce de correctif. Si un garde-fou doit tenir, renvoyez deny, pas ask, et testez-le dans votre propre version.
failClosed et timeout
Chaque entrée de hook peut avoir des options à côté de command :
failClosed: truetransforme un plantage, un dépassement du délai, un code de sortie non nul ou une sortie vide en blocage. La valeur par défaut estfalse: une garde défaillante laisse silencieusement tout passer. Pour un hook de sécurité, vous voulez presque toujourstrue.timeoutest exprimé en secondes. Prévoyez assez de marge si votre hook attend un humain : une personne qui lit un diff a besoin de plus de cinq secondes.matcherfiltre les cas où le hook s’exécute. Pour les hooks shell, il est comparé à la chaîne de commande complète ; pour les hooks d’outils, au type d’outil, commeShell,Read,WriteouMCP:<tool_name>.
Il y a un compromis. En juillet 2026, un utilisateur sous macOS a rencontré « MainThreadShellExec not initialized » : l’hôte shell de Cursor n’était pas prêt, et comme le hook était en fail-closed, le shell de l’agent était bloqué. L’équipe de Cursor a indiqué que le correctif avait été publié et a recommandé de garder failClosed: true. Si vous voyez cette erreur sur une version plus ancienne, le contournement consistait à lancer « Developer: Reload Window ».
Les hooks sous Windows
Sous Windows, l’application de bureau Cursor exécute les commandes des hooks via PowerShell et leur transmet le JSON par un pipe. Un script PowerShell le lit depuis $input ; tout autre programme lit l’entrée standard comme d’habitude. Trois points piègent souvent :
- Les scripts bash ne s’exécutent pas tels quels. Un
guard.shécrit par un collègue sous macOS échoue sous PowerShell. Utilisez un script.ps1, un programme compilé, ou appelez explicitement votre interpréteur (python .cursor/hooks/guard.py). Un fil du forum de janvier 2026 montre une configuration Git Bash qui échoue avecsyntax error near unexpected token '[Convert]::FromBase64String', c’est-à-dire du code PowerShell qui arrive dans bash. - La stratégie d’exécution. Si PowerShell refuse d’exécuter des scripts, lancez-les avec
powershell -NoProfile -ExecutionPolicy Bypass -File …, écrivez le JSON et terminez parexit 0. - Les programmes graphiques rendent la main trop tôt. Celui-ci nous a coûté une journée. Si votre hook est un programme Windows compilé pour le sous-système graphique, PowerShell le lance et continue sans attendre. Cursor lit une sortie vide, donc le hook semble n’avoir rien fait (ou, avec
failClosed, bloque tout). Compilez le hook comme programme console, ou encapsulez-le pour que PowerShell attende sa fin.
Les hooks de projet nécessitent aussi un espace de travail approuvé. Un membre de l’équipe de Cursor a listé ce qu’il faut vérifier quand ils ne font rien : la ligne « Hook N produced no output » dans le journal Hooks, l’espace de travail affiché comme approuvé dans Settings → Hooks, et des erreurs comme spawn, ENOENT, EPERM ou ExecutionPolicy dans la console.
Les hooks dans la CLI Cursor
La CLI Cursor (cursor-agent) lit les mêmes fichiers ~/.cursor/hooks.json et .cursor/hooks.json, mais elle n’a jamais envoyé tous les événements qu’envoie l’éditeur. En janvier 2026, seuls les deux hooks shell se déclenchaient. En avril, l’équipe de Cursor a confirmé que beforeShellExecution, afterShellExecution, afterFileEdit, postToolUse, stop et sessionStart fonctionnaient dans la CLI, alors que afterAgentResponse et afterAgentThought ne fonctionnaient toujours pas (confirmé à nouveau en mai). Pour tout événement absent de cette liste, testez avant de vous y fier.
Les agents cloud sont encore plus stricts : les hooks utilisateur de ~/.cursor/hooks.json ne s’y exécutent pas du tout.
Quand un hook ne se déclenche pas : liste de vérification
- Ouvrez le canal de sortie Hooks (View → Output → Hooks) ou l’onglet Hooks dans Customize. Il indique quels hooks ont été chargés, chaque exécution et les erreurs.
- Vérifiez le fichier JSON lui-même.
"version": 1présent, noms d’événements écrits exactement (ils sont sensibles à la casse), JSON valide. - Vérifiez le chemin. Les commandes relatives sont résolues depuis la racine du projet pour les hooks de projet, et depuis
~/.cursor/pour les hooks utilisateur. - Approuvez l’espace de travail. Les hooks de projet ne s’exécutent pas dans un dossier non approuvé.
- Exécutez le hook à la main. Envoyez-lui un exemple de JSON par un pipe et regardez stdout. Tout ce qui n’est pas un unique objet JSON (un
echoégaré, un avertissement) casse l’analyse. - Redémarrez Cursor. L’enregistrement recharge le fichier, mais plusieurs signalements du forum n’ont été résolus que par un redémarrage complet.
- Gardez en tête les limites connues.
asketallowont été signalés comme ignorés en présence de la liste d’autorisation et du bac à sable ;subagentStart/subagentStopont été signalés comme ne se déclenchant pas dans certaines versions ; la CLI ignore certains événements. Les commits faits depuis le panneau Source Control intégré ne sont pas interceptés : il n’existe pas d’événement de hook pre-commit.
Hooks, auto-run et Auto-review
Les hooks sont distincts des paramètres d’approbation propres à Cursor (Settings → Agents → Approvals & Execution). Auto-run et Auto-review décident si Cursor vous demande dans son panneau ; votre hook ajoute sa propre décision par-dessus. C’est pourquoi « mon hook a répondu ask, mais la commande s’est exécutée » est un signalement si fréquent : un autre chemin d’autorisation l’avait déjà autorisée.
Autre chose que nous avons appris à nos dépens : avec Auto-review, Cursor peut poser des questions sur certaines commandes dans sa propre fenêtre. Un hook ne voit jamais cette question. Elle se passe dans Cursor, pas dans le pipeline des hooks.
La place de Pet Island
Si ce que vous attendez des hooks, c’est « montre-moi chaque commande risquée et laisse-moi décider sans changer de fenêtre », c’est exactement ce que fait Pet Island avec Cursor. Il installe les hooks pour vous (beforeShellExecution et beforeMCPExecution, avec failClosed activé pour qu’un utilitaire arrêté ne laisse jamais passer une commande) et affiche chaque demande sur une petite île en haut de votre écran, avec la commande complète. Vous l’autorisez ou la refusez depuis l’île ; l’agent de Cursor attend.
Les commandes et les modifications de fichiers de l’éditeur Cursor fonctionnent depuis l’île. Les questions et les plans s’affichent sur l’île, et vous y répondez dans Cursor. Dans la CLI Cursor, les commandes fonctionnent depuis l’île ; les autres événements dépendent de ce qu’envoie la CLI. La désinstallation ne supprime que les entrées ajoutées par Pet Island ; vos propres hooks restent en place.
FAQ
Les hooks Cursor fonctionnent-ils sous Windows ?
Oui. L’application de bureau les exécute via PowerShell et transmet le JSON sur l’entrée standard ($input dans un script PowerShell). Les scripts bash écrits pour macOS nécessitent une version PowerShell ou Python, et les programmes du sous-système graphique doivent être encapsulés pour que PowerShell les attende.
Pourquoi mon hook renvoie-t-il ask alors que la commande s’exécute quand même ?
L’équipe de Cursor a indiqué en mars 2026 que les hooks n’appliquaient de façon fiable que deny ; allow et ask pouvaient être contournés par la liste d’autorisation ou le bac à sable. Utilisez deny pour tout ce qui ne doit pas s’exécuter, et consultez le canal de sortie Hooks pour voir ce que votre hook a réellement renvoyé.
À quoi sert failClosed ?
Avec failClosed défini sur true, un plantage, un dépassement du délai, un code de sortie non nul ou une sortie vide bloque l’action. Sans lui, un hook défaillant laisse l’action se poursuivre.
Où voir les erreurs des hooks ?
Dans le canal de sortie Hooks (View → Output → Hooks) et dans l’onglet Hooks de Customize.
Les hooks fonctionnent-ils dans la CLI Cursor ?
En partie. La CLI lit les mêmes fichiers hooks.json. En mai 2026, l’équipe de Cursor a confirmé les hooks shell, afterFileEdit, postToolUse, stop et sessionStart, mais pas afterAgentResponse ni afterAgentThought.
Un hook peut-il empêcher un commit git depuis le panneau Source Control ?
Non. Cursor n’a pas d’événement de hook pre-commit, et les commits effectués via l’interface Source Control intégrée ne sont pas interceptés. Utilisez pour cela un hook git pre-commit classique.
Sources
Chaque paramètre et chaque comportement décrits sur cette page ont été vérifiés dans ces pages le 26 octobre 2026. Les agents évoluent vite : si quelque chose ne correspond plus, c’est la page officielle qui fait foi.
- Documentation Cursor : Hooks
- Forum Cursor : les hooks Cursor ne s’exécutent pas comme prévu (équipe : seul deny est appliqué)
- Forum Cursor : allow/ask de beforeShellExecution ignorés, la liste d’autorisation est prioritaire
- Forum Cursor : hook fail-closed et MainThreadShellExec not initialized
- Forum Cursor : un JSON mal formé renvoyé par un hook autorisait silencieusement la commande (corrigé)
- Forum Cursor : la CLI Cursor n’envoie pas tous les événements de hook
- Forum Cursor : les hooks de projet ne s’exécutent pas sous Windows
- Forum Cursor : les hooks de projet échouent sous Windows avec Git Bash
- Forum Cursor : subagentStart et subagentStop ne se déclenchent jamais