Cursor-hooks: een complete handleiding, en waarom ze soms niet afgaan
Met Cursor-hooks kan een script meekijken met wat de agent doet of het blokkeren: elk shellcommando, elke MCP-aanroep, elke keer dat een bestand wordt gelezen of bewerkt. Ze zijn krachtig, en ze falen op stille manieren. Dit is alles wat we leerden toen we ze echt aansloten.
KORT ANTWOORD
Zet een hooks.json met "version": 1 in ~/.cursor/ (alle projecten) of in .cursor/ in een repo, koppel elk event aan een commando en druk JSON af op stdout. Gebruik beforeShellExecution en beforeMCPExecution om toe te staan, te weigeren of te vragen, en zet failClosed: true als een kapotte hook moet blokkeren in plaats van het commando door te laten. Lijkt een hook genegeerd te worden, open dan eerst het uitvoerkanaal Hooks: de meeste problemen zijn een pad, een workspace die niet wordt vertrouwd, ongeldige JSON of een event dat de Cursor CLI nog niet verstuurt.
Wat Cursor-hooks zijn
Een hook is een commando dat Cursor op een vast punt in de agentlus uitvoert. Cursor stuurt een JSON-object dat beschrijft wat er gaat gebeuren naar de standaardinvoer van het commando, wacht, en leest een JSON-antwoord uit de standaarduitvoer. Sommige hooks kijken alleen mee (de bewerking loggen, het bestand formatteren). Andere beslissen: een beforeShellExecution-hook kan een commando laten draaien, het stoppen of het eerst aan jou vragen.
Daarmee zijn hooks het juiste gereedschap voor drie taken: vangrails (nooit rm -rf of git push --force uitvoeren zonder een mens), automatisering (formatteren of linten na elke bewerking) en zichtbaarheid (loggen of doorsturen wat de agent doet, zodat je weet wanneer hij je nodig heeft).
Waar hooks.json staat
Cursor leest hooks van vier plekken en voert ze allemaal uit. Elke overeenkomende hook uit elke bron draait, en Cursor voegt de antwoorden samen.
| Niveau | Bestand | Opmerkingen |
|---|---|---|
| Gebruiker | ~/.cursor/hooks.json | Geldt voor elk project. Relatieve commando’s worden opgelost vanuit ~/.cursor/. |
| Project | <project>/.cursor/hooks.json | Ingecheckt in de repo. Draait alleen in een vertrouwde workspace. Relatieve commando’s worden opgelost vanuit de projectroot. |
| Team | Webdashboard van Cursor | Alleen Enterprise, gesynchroniseerd met alle leden. |
| Enterprise | C:\ProgramData\Cursor\hooks.json (Windows), /Library/Application Support/Cursor/hooks.json (macOS), /etc/cursor/hooks.json (Linux, WSL) | Beheerd door IT. |
Als antwoorden elkaar tegenspreken, wint het strengste: een deny uit welke bron dan ook gaat boven ask, en ask gaat boven allow. Het kleinste werkende bestand ziet er zo uit (het is het voorbeeld uit de documentatie van Cursor):
version moet 1 zijn. Cursor houdt het bestand in de gaten en laadt het opnieuw als je opslaat. Wordt een wijziging niet actief, start Cursor dan opnieuw. Alleen dat al lost verrassend veel meldingen op het forum op.
De events waar je een hook aan kunt hangen
De huidige lijst uit de documentatie van Cursor, gegroepeerd naar doel:
| Groep | Events | Kan het blokkeren? |
|---|---|---|
| Shell en MCP | beforeShellExecution, afterShellExecution, beforeMCPExecution, afterMCPExecution | De twee before…-hooks: toestaan, weigeren of vragen |
| Elke tool | preToolUse, postToolUse, postToolUseFailure | preToolUse: toestaan of weigeren (ask wordt momenteel niet afgedwongen) |
| Bestanden | beforeReadFile, afterFileEdit | beforeReadFile: toestaan of weigeren |
| Prompt | beforeSubmitPrompt | Ja, met continue: false |
| Agentlus | stop, afterAgentResponse, afterAgentThought, preCompact | Nee (stop kan een vervolgbericht sturen) |
| Subagents | subagentStart, subagentStop | subagentStart: toestaan of weigeren (ask telt als weigeren) |
| Sessie en app | sessionStart, sessionEnd, workspaceOpen | Nee |
| Tab-aanvullingen | beforeTabFileRead, afterTabFileEdit | beforeTabFileRead: toestaan of weigeren |
Elk event krijgt een paar gemeenschappelijke velden (conversation_id, generation_id, model, hook_event_name, cursor_version, workspace_roots) plus eigen velden. beforeShellExecution krijgt de volledige command en de cwd; stop krijgt een status met de waarde completed, aborted of error.
Toestaan, weigeren en vragen
Een beslissende hook drukt één JSON-object af. Voor de shellhook ziet dat er zo uit:
permissionisallow,denyofask.user_messageis wat jij in Cursor ziet.agent_messageis wat de agent te horen krijgt, zodat hij van koers kan veranderen in plaats van hetzelfde commando opnieuw te proberen.
Afsluitcodes zijn net zo belangrijk als de JSON. Afsluitcode 0 betekent “gebruik mijn JSON”. Afsluitcode 2 blokkeert de actie, net als deny. Elke andere afsluitcode wordt behandeld als een mislukte hook, en de actie gaat door, tenzij je failClosed instelt. Bij machtigingshooks blokkeert ongeldige JSON of JSON die niet aan het schema voldoet de actie.
De documentatie van Cursor vermeldt ask als ondersteund voor beforeShellExecution en beforeMCPExecution. In maart 2026 schreven medewerkers van Cursor op het forum dat hooks alleen deny afdwongen en dat allow en ask “basically ignored” werden, vooral met de allowlist voor commando’s of de sandbox aan. We hebben geen aankondiging van een oplossing gevonden. Moet een vangrail standhouden, geef dan deny terug, niet ask, en test het in je eigen versie.
failClosed en timeout
Elke hookvermelding kan naast command opties bevatten:
failClosed: truemaakt van een crash, een time-out, een afsluitcode anders dan nul of lege uitvoer een blokkade. De standaard isfalse: een kapotte bewaking laat stilletjes alles door. Voor een veiligheidshook wil je bijna altijdtrue.timeoutis in seconden. Laat genoeg ruimte als je hook op een mens wacht: iemand die een diff leest, heeft meer dan vijf seconden nodig.matcherbepaalt wanneer de hook draait. Bij shellhooks wordt hij vergeleken met de volledige commandostring; bij toolhooks met het tooltype, zoalsShell,Read,WriteofMCP:<tool_name>.
Er zit een afweging aan. In juli 2026 kreeg een gebruiker op macOS “MainThreadShellExec not initialized”: de eigen shellhost van Cursor was niet klaar, en omdat de hook fail-closed was, werd de shell van de agent geblokkeerd. Medewerkers van Cursor zeiden dat de oplossing was uitgebracht en raadden aan failClosed: true te houden. Zie je die fout in een oudere build, dan was “Developer: Reload Window” de workaround.
Hooks op Windows
Op Windows voert de Cursor-desktopapp hookcommando’s uit via PowerShell en geeft de JSON door via een pipe. Een PowerShell-script leest die uit $input; elk ander programma leest gewoon de standaardinvoer. Drie dingen gaan vaak mis:
- Bash-scripts draaien niet zomaar. Een
guard.shvan een teamgenoot op macOS mislukt onder PowerShell. Gebruik een.ps1-script, een gecompileerd programma, of roep je interpreter expliciet aan (python .cursor/hooks/guard.py). Een forumdiscussie uit januari 2026 laat een Git Bash-configuratie zien die mislukt metsyntax error near unexpected token '[Convert]::FromBase64String', en dat is PowerShell-code die in bash terechtkomt. - Uitvoeringsbeleid. Weigert PowerShell scripts uit te voeren, start ze dan met
powershell -NoProfile -ExecutionPolicy Bypass -File …, druk de JSON af en sluit af metexit 0. - GUI-programma’s keren te vroeg terug. Dit kostte ons een dag. Is je hook een Windows-programma dat voor het GUI-subsysteem is gebouwd, dan start PowerShell het en gaat verder zonder te wachten. Cursor leest lege uitvoer, dus de hook lijkt niets te doen (of blokkeert met
failClosedalles). Bouw de hook als consoleprogramma, of verpak hem zo dat PowerShell wacht tot hij afsluit.
Hooks op projectniveau hebben ook een vertrouwde workspace nodig. Een medewerker van Cursor somde op wat je moet controleren als ze niets doen: de logregel “Hook N produced no output” in Hooks, of Settings → Hooks de workspace als vertrouwd toont, en fouten zoals spawn, ENOENT, EPERM of ExecutionPolicy in de console.
Hooks in de Cursor CLI
De Cursor CLI (cursor-agent) leest dezelfde ~/.cursor/hooks.json en .cursor/hooks.json, maar heeft nooit elk event verstuurd dat de editor verstuurt. In januari 2026 gingen alleen de twee shellhooks af. In april bevestigden medewerkers van Cursor dat beforeShellExecution, afterShellExecution, afterFileEdit, postToolUse, stop en sessionStart werkten in de CLI, terwijl afterAgentResponse en afterAgentThought dat nog steeds niet deden (in mei opnieuw bevestigd). Voor elk event dat niet op die lijst staat geldt: test het voordat je erop vertrouwt.
Cloud agents zijn nog strenger: hooks op gebruikersniveau uit ~/.cursor/hooks.json draaien daar helemaal niet.
Als een hook niet afgaat: een checklist
- Open het uitvoerkanaal Hooks (View → Output → Hooks) of het tabblad Hooks in Customize. Daar zie je welke hooks zijn geladen, elke uitvoering en fouten.
- Controleer het JSON-bestand zelf.
"version": 1aanwezig, eventnamen exact gespeld (ze zijn hoofdlettergevoelig), geldige JSON. - Controleer het pad. Relatieve commando’s worden voor projecthooks opgelost vanuit de projectroot en voor gebruikershooks vanuit
~/.cursor/. - Vertrouw de workspace. Projecthooks draaien niet in een niet-vertrouwde map.
- Voer de hook met de hand uit. Stuur er voorbeeld-JSON naartoe via een pipe en bekijk stdout. Alles behalve één JSON-object (een verdwaalde
echo, een waarschuwing) breekt het parsen. - Start Cursor opnieuw. Opslaan laadt het bestand opnieuw, maar meerdere forummeldingen werden pas opgelost met een volledige herstart.
- Ken de beperkingen.
askenallowwerden gemeld als genegeerd naast de allowlist en de sandbox;subagentStart/subagentStopgingen volgens meldingen in sommige versies niet af; de CLI slaat sommige events over. Commits vanuit het ingebouwde paneel Source Control worden niet onderschept: er is geen hookevent voor pre-commit.
Hooks, auto-run en Auto-review
Hooks staan los van de eigen goedkeuringsinstellingen van Cursor (Settings → Agents → Approvals & Execution). Auto-run en Auto-review bepalen of Cursor het je in zijn paneel vraagt; je hook voegt daar zijn eigen beslissing aan toe. Daarom is “mijn hook zei ask, maar het commando draaide gewoon” zo’n veelvoorkomende melding: een ander machtigingspad had het al toegestaan.
Nog iets wat we op de harde manier leerden: met Auto-review kan Cursor over sommige commando’s vragen in zijn eigen venster. Een hook ziet die vraag nooit. Die gebeurt in Cursor, niet in de hookpipeline.
Waar Pet Island in beeld komt
Wil je van hooks vooral “laat me elk riskant commando zien en laat me beslissen zonder van venster te wisselen”, dan is dat wat Pet Island met Cursor doet. Het installeert de hooks voor je (beforeShellExecution en beforeMCPExecution, met failClosed aan zodat een vastgelopen helper nooit een commando doorlaat) en zet elk verzoek met het volledige commando op een klein eiland bovenaan je scherm. Daar sta je het toe of weiger je het; de agent van Cursor wacht.
Commando’s en bestandsbewerkingen uit de Cursor-editor werken vanaf het eiland. Vragen en plannen verschijnen op het eiland, en je beantwoordt ze in Cursor. In de Cursor CLI werken commando’s vanaf het eiland; andere events hangen af van wat de CLI verstuurt. Bij het verwijderen worden alleen de vermeldingen verwijderd die Pet Island heeft toegevoegd; je eigen hooks blijven staan.
VEELGESTELDE VRAGEN
Werken Cursor-hooks op Windows?
Ja. De desktopapp voert ze uit via PowerShell en geeft de JSON door via de standaardinvoer ($input in een PowerShell-script). Bash-scripts die voor macOS zijn geschreven, hebben een PowerShell- of Python-versie nodig, en programma’s uit het GUI-subsysteem moeten zo worden verpakt dat PowerShell erop wacht.
Waarom geeft mijn hook ask terug maar draait het commando toch?
Medewerkers van Cursor zeiden in maart 2026 dat hooks alleen deny betrouwbaar afdwongen; allow en ask konden worden overschreven door de allowlist of de sandbox. Gebruik deny voor alles wat niet mag draaien, en kijk in het uitvoerkanaal Hooks wat je hook echt heeft teruggegeven.
Wat doet failClosed?
Met failClosed op true blokkeert een crash, time-out, afsluitcode anders dan nul of lege uitvoer de actie. Zonder die instelling laat een kapotte hook de actie doorgaan.
Waar zie ik fouten van hooks?
In het uitvoerkanaal Hooks (View → Output → Hooks) en het tabblad Hooks in Customize.
Werken hooks in de Cursor CLI?
Gedeeltelijk. De CLI leest dezelfde hooks.json-bestanden. In mei 2026 bevestigden medewerkers van Cursor de shellhooks, afterFileEdit, postToolUse, stop en sessionStart, maar niet afterAgentResponse of afterAgentThought.
Kan een hook een git-commit vanuit het paneel Source Control tegenhouden?
Nee. Cursor heeft geen hookevent voor pre-commit, en commits via de ingebouwde Source Control-interface worden niet onderschept. Gebruik daarvoor een gewone git pre-commit-hook.
Bronnen
Elke instelling en elk gedrag op deze pagina is op 26 oktober 2026 gecontroleerd aan de hand van deze pagina’s. Agents veranderen snel; klopt er iets niet meer, dan geldt de officiële pagina.
- Cursor-documentatie: Hooks
- Cursor-forum: The cursor hooks did not execute as expected (medewerker: alleen deny wordt afgedwongen)
- Cursor-forum: beforeShellExecution allow/ask ignored, allow-list takes precedence
- Cursor-forum: Fail-closed hook and MainThreadShellExec not initialized
- Cursor-forum: Malformed JSON from a hook silently allowed the command (opgelost)
- Cursor-forum: Cursor CLI doesn’t send all hook events
- Cursor-forum: Project-level hooks fail to execute on Windows
- Cursor-forum: Project hooks fail on Windows with Git Bash
- Cursor-forum: subagentStart and subagentStop never fire