Cursor-Hooks: ein vollständiger Guide und warum sie manchmal nicht auslösen

Mit Cursor-Hooks kann ein Skript beobachten oder blockieren, was der Agent tut: jeden Shell-Befehl, jeden MCP-Aufruf, jedes Lesen und Bearbeiten von Dateien. Sie sind mächtig, scheitern aber leise. Hier steht alles, was wir gelernt haben, als wir sie wirklich eingerichtet haben.

Von Aslan, Pet-Island-Team · Aktualisiert am 26. Okt. 2026 · 7 Min. Lesezeit

KURZE ANTWORT

Leg eine hooks.json mit "version": 1 in ~/.cursor/ (alle Projekte) oder in .cursor/ in einem Repo ab, verweise jedes Event auf einen Befehl und gib JSON auf stdout aus. Mit beforeShellExecution und beforeMCPExecution erlaubst, verweigerst oder fragst du, und mit failClosed: true sorgst du dafür, dass ein defekter Hook blockiert, statt den Befehl durchzulassen. Wenn ein Hook ignoriert zu werden scheint, öffne zuerst den Ausgabekanal Hooks: Meist liegt es an einem Pfad, einem nicht vertrauenswürdigen Workspace, ungültigem JSON oder einem Event, das die Cursor CLI noch nicht sendet.

Was Cursor-Hooks sind

Ein Hook ist ein Befehl, den Cursor an einem festen Punkt der Agent-Schleife ausführt. Cursor schickt ein JSON-Objekt, das beschreibt, was gleich passiert, an die Standardeingabe des Befehls, wartet und liest eine JSON-Antwort aus dessen Standardausgabe. Manche Hooks beobachten nur (Änderung loggen, Datei formatieren). Andere entscheiden: Ein beforeShellExecution-Hook kann einen Befehl laufen lassen, ihn stoppen oder dich vorher fragen.

Damit sind Hooks das richtige Werkzeug für drei Aufgaben: Leitplanken (niemals rm -rf oder git push --force ohne einen Menschen ausführen), Automatisierung (nach jeder Änderung formatieren oder linten) und Sichtbarkeit (protokollieren oder weiterleiten, was der Agent tut, damit du weißt, wann er dich braucht).

Wo hooks.json liegt

Cursor liest Hooks aus vier Orten und führt alle aus. Jeder passende Hook aus jeder Quelle läuft, und Cursor führt ihre Antworten zusammen.

EbeneDateiHinweise
Benutzer~/.cursor/hooks.jsonGilt für jedes Projekt. Relative Befehle werden ab ~/.cursor/ aufgelöst.
Projekt<project>/.cursor/hooks.jsonIns Repo eingecheckt. Läuft nur in einem vertrauenswürdigen Workspace. Relative Befehle werden ab dem Projektstamm aufgelöst.
TeamCursor-Web-DashboardNur Enterprise, wird an jedes Mitglied synchronisiert.
EnterpriseC:\ProgramData\Cursor\hooks.json (Windows), /Library/Application Support/Cursor/hooks.json (macOS), /etc/cursor/hooks.json (Linux, WSL)Von der IT verwaltet.

Wenn Antworten sich widersprechen, gewinnt die strengste: Ein deny aus irgendeiner Quelle schlägt ask, und ask schlägt allow. Die kleinste funktionierende Datei sieht so aus (es ist das Beispiel aus Cursors Doku):

~/.cursor/hooks.jsonKopieren
{
"version": 1,
"hooks": {
"afterFileEdit": [{ "command": "./hooks/format.sh" }]
}
}

version muss 1 sein. Cursor überwacht die Datei und lädt sie beim Speichern neu. Wenn eine Änderung nicht greift, starte Cursor neu. Allein das löst erstaunlich viele Meldungen im Forum.

Die Events, an die du Hooks hängen kannst

Die aktuelle Liste aus Cursors Doku, gruppiert nach Zweck:

GruppeEventsKann es blockieren?
Shell und MCPbeforeShellExecution, afterShellExecution, beforeMCPExecution, afterMCPExecutionDie beiden before…-Hooks: erlauben, verweigern oder fragen
Beliebiges ToolpreToolUse, postToolUse, postToolUseFailurepreToolUse: erlauben oder verweigern (ask wird derzeit nicht durchgesetzt)
DateienbeforeReadFile, afterFileEditbeforeReadFile: erlauben oder verweigern
PromptbeforeSubmitPromptJa, mit continue: false
Agent-Schleifestop, afterAgentResponse, afterAgentThought, preCompactNein (stop kann eine Folgenachricht senden)
SubagentensubagentStart, subagentStopsubagentStart: erlauben oder verweigern (ask zählt als deny)
Sitzung und AppsessionStart, sessionEnd, workspaceOpenNein
Tab-VervollständigungenbeforeTabFileRead, afterTabFileEditbeforeTabFileRead: erlauben oder verweigern

Jedes Event erhält einige gemeinsame Felder (conversation_id, generation_id, model, hook_event_name, cursor_version, workspace_roots) plus eigene. beforeShellExecution bekommt den vollständigen command und dessen cwd; stop bekommt einen status mit completed, aborted oder error.

Erlauben, verweigern und fragen

Ein entscheidender Hook gibt ein JSON-Objekt aus. Für den Shell-Hook sieht das so aus:

stdout eines beforeShellExecution-HooksKopieren
{
"permission": "ask",
"user_message": "This pushes to main. Run it?",
"agent_message": "The user wants to confirm pushes."
}
  • permission ist allow, deny oder ask.
  • user_message ist das, was du in Cursor siehst.
  • agent_message ist das, was dem Agenten mitgeteilt wird, damit er umsteuern kann, statt denselben Befehl erneut zu versuchen.

Exit-Codes sind genauso wichtig wie das JSON. Exit 0 heißt „nimm mein JSON“. Exit 2 blockiert die Aktion, genau wie deny. Jeder andere Exit-Code gilt als fehlgeschlagener Hook, und die Aktion läuft weiter, es sei denn, du setzt failClosed. Bei Berechtigungs-Hooks blockiert ungültiges oder nicht schemakonformes JSON die Aktion.

Cursors Doku führt ask als unterstützt für beforeShellExecution und beforeMCPExecution. Im März 2026 schrieben Cursor-Mitarbeiter im Forum, dass Hooks nur deny durchsetzten und allow sowie ask „basically ignored“ (praktisch ignoriert) würden, besonders bei aktiver Befehls-Allowlist oder Sandbox. Eine Ankündigung zu einem Fix haben wir nicht gefunden. Wenn eine Leitplanke unbedingt halten muss, gib deny zurück, nicht ask, und teste es in deiner eigenen Version.

failClosed und timeout

Jeder Hook-Eintrag kann neben command Optionen enthalten:

.cursor/hooks.jsonKopieren
{
"version": 1,
"hooks": {
"beforeShellExecution": [
{
"command": "./.cursor/hooks/guard.sh",
"timeout": 30,
"failClosed": true
}
]
}
}
  • failClosed: true macht aus einem Absturz, einem Timeout, einem Exit-Code ungleich null oder leerer Ausgabe eine Blockade. Der Standard ist false: Ein defekter Guard lässt stillschweigend alles durch. Für einen Sicherheits-Hook willst du fast immer true.
  • timeout wird in Sekunden angegeben. Lass genug Spielraum, wenn dein Hook auf einen Menschen wartet: Wer einen Diff liest, braucht mehr als fünf Sekunden.
  • matcher filtert, wann der Hook läuft. Bei Shell-Hooks wird er mit dem vollständigen Befehlsstring abgeglichen, bei Tool-Hooks mit dem Tool-Typ, etwa Shell, Read, Write oder MCP:<tool_name>.

Das hat seinen Preis. Im Juli 2026 stieß ein Nutzer unter macOS auf „MainThreadShellExec not initialized“: Cursors eigener Shell-Host war nicht bereit, und weil der Hook fail-closed war, wurde die Shell des Agenten blockiert. Cursor-Mitarbeiter sagten, der Fix sei ausgeliefert, und empfahlen, failClosed: true beizubehalten. Wenn du den Fehler auf einem älteren Build siehst, war „Developer: Reload Window“ der Workaround.

Hooks unter Windows

Unter Windows führt die Cursor-Desktop-App Hook-Befehle über PowerShell aus und leitet das JSON per Pipe hinein. Ein PowerShell-Skript liest es aus $input; jedes andere Programm liest wie gewohnt die Standardeingabe. Drei Dinge sorgen oft für Probleme:

  1. Bash-Skripte laufen nicht unverändert. Ein guard.sh von einer Teamkollegin mit macOS scheitert unter PowerShell. Verwende ein .ps1-Skript, ein kompiliertes Programm oder ruf deinen Interpreter explizit auf (python .cursor/hooks/guard.py). Ein Forenthread vom Januar 2026 zeigt ein Git-Bash-Setup, das mit syntax error near unexpected token '[Convert]::FromBase64String' scheitert: PowerShell-Code, der in bash landet.
  2. Execution Policy. Wenn PowerShell sich weigert, Skripte auszuführen, starte sie mit powershell -NoProfile -ExecutionPolicy Bypass -File …, gib das JSON aus und beende mit exit 0.
  3. GUI-Programme kehren zu früh zurück. Das hat uns einen Tag gekostet. Wenn dein Hook ein Windows-Programm für das GUI-Subsystem ist, startet PowerShell es und macht weiter, ohne zu warten. Cursor liest eine leere Ausgabe, also sieht es aus, als hätte der Hook nichts getan (oder er blockiert mit failClosed alles). Baue den Hook als Konsolenprogramm oder kapsle ihn so, dass PowerShell auf sein Ende wartet.

Hooks auf Projektebene brauchen außerdem einen vertrauenswürdigen Workspace. Ein Cursor-Mitarbeiter hat aufgelistet, was zu prüfen ist, wenn sie nichts tun: die Zeile „Hook N produced no output“ im Hooks-Log, ob Settings → Hooks den Workspace als vertrauenswürdig anzeigt, und Fehler wie spawn, ENOENT, EPERM oder ExecutionPolicy in der Konsole.

Hooks in der Cursor CLI

Die Cursor CLI (cursor-agent) liest dieselben Dateien ~/.cursor/hooks.json und .cursor/hooks.json, hat aber noch nie alle Events gesendet, die der Editor sendet. Im Januar 2026 lösten nur die beiden Shell-Hooks aus. Im April bestätigten Cursor-Mitarbeiter, dass beforeShellExecution, afterShellExecution, afterFileEdit, postToolUse, stop und sessionStart in der CLI funktionierten, afterAgentResponse und afterAgentThought dagegen weiterhin nicht (im Mai erneut bestätigt). Für jedes Event, das nicht auf dieser Liste steht, gilt: testen, bevor du dich darauf verlässt.

Cloud-Agenten sind noch strenger: Hooks auf Benutzerebene aus ~/.cursor/hooks.json laufen dort überhaupt nicht.

Wenn ein Hook nicht auslöst: eine Checkliste

  1. Öffne den Ausgabekanal Hooks (View → Output → Hooks) oder den Tab Hooks unter Customize. Dort siehst du, welche Hooks geladen wurden, jeden Lauf und alle Fehler.
  2. Prüf die JSON-Datei selbst. "version": 1 vorhanden, Event-Namen exakt geschrieben (Groß- und Kleinschreibung zählt), gültiges JSON.
  3. Prüf den Pfad. Relative Befehle werden bei Projekt-Hooks ab dem Projektstamm aufgelöst, bei Benutzer-Hooks ab ~/.cursor/.
  4. Vertrau dem Workspace. Projekt-Hooks laufen nicht in einem nicht vertrauenswürdigen Ordner.
  5. Führ den Hook von Hand aus. Leite ein Beispiel-JSON per Pipe hinein und sieh dir stdout an. Alles außer einem einzigen JSON-Objekt (ein verirrtes echo, eine Warnung) bricht das Parsen.
  6. Starte Cursor neu. Speichern lädt die Datei neu, aber mehrere Forenmeldungen ließen sich erst durch einen kompletten Neustart beheben.
  7. Kenne die Lücken. ask und allow wurden neben Allowlist und Sandbox als ignoriert gemeldet; subagentStart/subagentStop lösten Berichten zufolge in einigen Versionen nicht aus; die CLI überspringt manche Events. Commits aus dem integrierten Source-Control-Panel werden nicht abgefangen: Es gibt kein Pre-Commit-Hook-Event.

Hooks, Auto-run und Auto-review

Hooks sind von Cursors eigenen Freigabeeinstellungen getrennt (Settings → Agents → Approvals & Execution). Auto-run und Auto-review entscheiden, ob Cursor dich in seinem Panel fragt; dein Hook fügt seine eigene Entscheidung obendrauf. Deshalb ist „mein Hook hat ask gesagt, aber der Befehl lief einfach“ eine so häufige Meldung: Ein anderer Berechtigungsweg hatte ihn bereits erlaubt.

Noch etwas, das wir auf die harte Tour gelernt haben: Mit Auto-review kann Cursor bei manchen Befehlen in seinem eigenen Fenster fragen. Ein Hook sieht diese Frage nie. Sie entsteht in Cursor, nicht in der Hook-Pipeline.

Wo Pet Island ins Spiel kommt

Wenn du von Hooks eigentlich willst: „Zeig mir jeden riskanten Befehl und lass mich entscheiden, ohne das Fenster zu wechseln“, dann ist das genau das, was Pet Island mit Cursor macht. Es installiert die Hooks für dich (beforeShellExecution und beforeMCPExecution, mit aktiviertem failClosed, damit ein abgestürzter Helfer nie einen Befehl durchlässt) und zeigt jede Anfrage mit dem vollständigen Befehl auf einer kleinen Insel oben auf deinem Bildschirm. Dort erlaubst oder verweigerst du sie; Cursors eigener Agent wartet.

Befehle und Dateiänderungen aus dem Cursor-Editor funktionieren über die Insel. Fragen und Pläne erscheinen auf der Insel, beantwortet werden sie in Cursor. In der Cursor CLI funktionieren Befehle über die Insel; andere Events hängen davon ab, was die CLI sendet. Beim Deinstallieren werden nur die Einträge entfernt, die Pet Island hinzugefügt hat; deine eigenen Hooks bleiben.

FAQ

Funktionieren Cursor-Hooks unter Windows?

Ja. Die Desktop-App führt sie über PowerShell aus und übergibt das JSON über die Standardeingabe ($input in einem PowerShell-Skript). Für macOS geschriebene Bash-Skripte brauchen eine PowerShell- oder Python-Version, und Programme für das GUI-Subsystem müssen so gekapselt werden, dass PowerShell auf sie wartet.

Warum gibt mein Hook ask zurück, und der Befehl läuft trotzdem?

Cursor-Mitarbeiter sagten im März 2026, dass Hooks nur deny zuverlässig durchsetzten; allow und ask konnten von der Allowlist oder der Sandbox übersteuert werden. Verwende deny für alles, was auf keinen Fall laufen darf, und prüf im Ausgabekanal Hooks, was dein Hook tatsächlich zurückgegeben hat.

Was macht failClosed?

Mit failClosed auf true blockieren ein Absturz, ein Timeout, ein Exit-Code ungleich null oder leere Ausgabe die Aktion. Ohne diese Option lässt ein defekter Hook die Aktion weiterlaufen.

Wo sehe ich Hook-Fehler?

Im Ausgabekanal Hooks (View → Output → Hooks) und im Tab Hooks unter Customize.

Funktionieren Hooks in der Cursor CLI?

Teilweise. Die CLI liest dieselben hooks.json-Dateien. Stand Mai 2026 bestätigten Cursor-Mitarbeiter die Shell-Hooks, afterFileEdit, postToolUse, stop und sessionStart, nicht aber afterAgentResponse oder afterAgentThought.

Kann ein Hook einen Git-Commit aus dem Source-Control-Panel stoppen?

Nein. Cursor hat kein Pre-Commit-Hook-Event, und Commits über die integrierte Source-Control-Oberfläche werden nicht abgefangen. Verwende dafür einen normalen Git-Pre-Commit-Hook.

Quellen

Jede Einstellung und jedes Verhalten auf dieser Seite wurde am 26. Oktober 2026 mit diesen Seiten abgeglichen. Agenten ändern sich schnell; wenn etwas nicht mehr passt, gilt die offizielle Seite.

WEITERLESEN

Antworte jedem Agenten direkt oben auf deinem Bildschirm.

Windows und Mac · 7 Tage kostenlos, dann einmalig 8,99 $ · 1 $ pro Verkauf ernährt Straßentiere

Pet Island kostenlos testen