Claude Code Hooks: praktischer Leitfaden mit Beispielen

So funktionieren Claude Code Hooks: Events, settings.json, Matcher, Exit-Codes und JSON-Ausgabe, dazu fertige Hooks zum Formatieren, Blockieren und Benachrichtigen.

Solviera Teknoloji 10 Min. Lesezeit

Claude Code Hooks sind Befehle, die Claude Code an festen Punkten einer Session selbstständig ausführt: vor einem Tool-Aufruf, nach einer Dateiänderung, beim Absenden eines Prompts, wenn Claude stoppt oder deine Aufmerksamkeit braucht. Du definierst sie unter dem Schlüssel hooks in settings.json. Ein Hook kann Kontext hinzufügen, eine Datei formatieren oder eine Aktion blockieren, indem er mit Code 2 endet oder eine JSON-Entscheidung zurückgibt. Anders als Anweisungen in CLAUDE.md läuft ein Hook immer, weil ihn das Harness ausführt, nicht das Modell.

Dieser Leitfaden behandelt die wichtigen Events, das Konfigurationsformat, Matcher, Exit-Codes und JSON-Ausgabe, drei Hooks zum Kopieren und die Sicherheitshinweise, die du vor dem ersten Hook lesen solltest. Die Fakten stammen aus der offiziellen Hooks-Referenz und dem Hooks-Guide. Wenn du nur die kurze Definition suchst, schau in den Glossareintrag zu Claude Code Hooks.

Warum Hooks statt Anweisungen

Du kannst „Nach jeder Änderung immer Prettier ausführen“ in CLAUDE.md schreiben, und Claude wird es meistens tun. Meistens ist das Problem. Anweisungen sind Hinweise, die das Modell gegen alles andere in seinem Kontext abwägt. Hooks sind Code, der jedes Mal läuft, wenn das Event feuert.

Deshalb sind Hooks das richtige Werkzeug für drei Arten von Regeln:

  • Dinge, die immer passieren müssen, wie Formatieren, Linten oder Logging.
  • Dinge, die nie passieren dürfen, wie .env ändern oder rm -rf im falschen Ordner ausführen.
  • Dinge, von denen du wissen willst, etwa dass Claude auf eine Berechtigungsantwort wartet, während du in einem anderen Fenster bist.

Lass Stilhinweise und Projektwissen in CLAUDE.md und lege harte Regeln in Hooks.

Hook-Events, die du wirklich brauchst

Die Referenz listet mehr als dreißig Events, und die Liste wächst mit neuen Releases. Die meisten echten Setups nutzen eine Handvoll:

EventWann es feuertKann es blockieren?
PreToolUseBevor ein Tool-Aufruf läuftJa, blockiert den Aufruf
PostToolUseNachdem ein Tool-Aufruf erfolgreich warNein, das Tool ist schon gelaufen; stderr geht an Claude
UserPromptSubmitWenn du einen Prompt abschickstJa, der Prompt erreicht Claude nie
NotificationWenn Claude Code eine Benachrichtigung sendet, etwa eine Berechtigungsabfrage oder Leerlauf-WartezeitNein
StopWenn Claude seine Antwort beendetJa, Claude arbeitet weiter
SubagentStart / SubagentStopWenn ein Subagent startet oder endetNur SubagentStop
SessionStartBeim Start, beim Fortsetzen, bei /clear oder beim KomprimierenNein, aber stdout wird zu Kontext
PreCompactBevor der Kontext komprimiert wirdJa, blockiert das Komprimieren
PermissionRequestKurz bevor ein Berechtigungsdialog erscheintNur über JSON, nicht über Exit 2
SessionEndWenn die Session endetNein

PreToolUse und PostToolUse decken die meisten Bedürfnisse ab. Notification und Stop sind die für „sag mir Bescheid, wenn es fertig ist oder hängt“.

So konfigurierst du Hooks in settings.json

Hooks liegen in denselben Settings-Dateien wie Berechtigungen:

DateiGeltungsbereichÜber Git geteilt?
~/.claude/settings.jsonAlle deine ProjekteNein
.claude/settings.jsonEin ProjektJa
.claude/settings.local.jsonEin Projekt, nur duNein (gitignored)
Managed Policy SettingsGanze OrganisationVon Admins gesetzt

Plugins können Hooks in hooks/hooks.json mitliefern, und Skills und Subagenten können Hooks in ihrem Frontmatter definieren.

Die Struktur hat drei Ebenen: das Event, eine Matcher-Gruppe und eine Liste von Handlern.

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "jq -r '.tool_input.file_path' | xargs npx prettier --write",
            "timeout": 30
          }
        ]
      }
    ]
  }
}

Der type eines Handlers kann command (ein Shell-Befehl), http (ein POST an eine URL), mcp_tool (ein Tool eines verbundenen MCP-Servers), prompt (eine schnelle Ja/Nein-Prüfung durch ein Modell) oder agent (eine experimentelle Subagenten-Prüfung) sein. Das Standard-Timeout beträgt 600 Sekunden für Command-, HTTP- und MCP-Tool-Handler, 30 Sekunden für Prompt-Hooks und 60 für Agent-Hooks.

Jeder Hook bekommt JSON über stdin. Übliche Felder sind session_id, transcript_path, cwd, permission_mode und hook_event_name. Tool-Events ergänzen tool_name, tool_input und tool_use_id. Dein Skript liest dieses JSON, meist mit jq, und entscheidet, was zu tun ist. Die Umgebungsvariable CLAUDE_PROJECT_DIR zeigt auf die Projektwurzel, praktisch, um Skripte aus dem Repo zu referenzieren.

Mit /hooks in Claude Code stöberst du durch die geladenen Hooks und siehst, aus welcher Datei jeder stammt. Der Browser ist schreibgeschützt, Hooks änderst du also durch Bearbeiten des JSON.

Matcher: welche Tool-Aufrufe ein Hook sieht

Der matcher bestimmt, welche Aufrufe einen Hook auslösen. Bei Tool-Events trifft er auf den Tool-Namen:

MatcherBedeutung
"*", "" oder kein MatcherJeder Aufruf
"Bash"Genau das Bash-Tool
"Edit|Write"Edit oder Write (eine Liste)
"mcp__memory__.*"Jedes Tool des MCP-Servers memory
"mcp__.*__write.*"Jedes MCP-Tool, dessen Name mit write beginnt

Die Regel ist einfach. Besteht der Matcher nur aus Buchstaben, Ziffern, _, -, Leerzeichen, Kommas oder |, ist er ein exakter Name oder eine Namensliste. Jedes andere Zeichen macht ihn zu einem regulären JavaScript-Ausdruck. MCP-Tools heißen mcp__<server>__<tool>.

Manche Events ignorieren den Matcher, darunter UserPromptSubmit und Stop, weil es nichts zu treffen gibt. Andere treffen etwas anderes als einen Tool-Namen. SessionStart trifft, wie die Session gestartet wurde (startup, resume, clear, compact), und Notification trifft den Benachrichtigungstyp, etwa permission_prompt oder idle_prompt.

Tool-Events akzeptieren auch ein if-Feld mit der Syntax von Berechtigungsregeln, sodass ein Bash-Hook zum Beispiel nur für "Bash(git *)" läuft. Die Doku beschreibt das als Best-Effort-Filter, halte die eigentliche Prüfung also in deinem Skript.

Exit-Codes und JSON-Ausgabe

Ein Command-Hook antwortet Claude Code auf zwei Wegen: über den Exit-Code und über optionales JSON auf stdout.

Exit-CodeWas passiert
0Erfolg. stdout wird als JSON geparst, wenn es ein einzelnes JSON-Objekt ist. Bei UserPromptSubmit und SessionStart wird Klartext auf stdout dem Kontext von Claude hinzugefügt.
2Blockierender Fehler. stderr ist der Grund. Bei PreToolUse wird der Aufruf abgebrochen, bei UserPromptSubmit wird der Prompt abgelehnt, bei Stop arbeitet Claude weiter.
Alles andere, auch 1Nicht blockierender Fehler. Claude Code vermerkt ihn und macht weiter.

Die Falle ist Exit-Code 1. Viele Skripte scheitern standardmäßig mit 1, und das blockiert nichts. Wenn du „stopp“ meinst, beende mit 2.

Für feinere Steuerung beende mit 0 und gib JSON aus. Ein PreToolUse-Hook kann erlauben, ablehnen oder nachfragen:

{
  "hookSpecificOutput": {
    "hookEventName": "PreToolUse",
    "permissionDecision": "deny",
    "permissionDecisionReason": "Use rg instead of grep for better performance"
  }
}

permissionDecision akzeptiert allow, deny, ask oder defer. Ein Hook kann den Aufruf auch mit updatedInput umschreiben oder Claude mit additionalContext zusätzlichen Text mitgeben. Felder, die bei jedem Event funktionieren, sind continue: false (Claude ganz stoppen, mit einem stopReason) und systemMessage (eine Warnung für dich).

Treffen mehrere Hooks zu, laufen sie parallel, und die restriktivste Antwort gewinnt: deny schlägt defer, defer schlägt ask, ask schlägt allow. Ein allow von einem Hook überstimmt keine Deny-Regel in deinen Berechtigungen.

Beispiel 1: Dateien nach jeder Änderung formatieren

Mit diesem Hook starten die meisten. Er führt Prettier auf jeder Datei aus, die Claude schreibt oder ändert:

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "jq -r '.tool_input.file_path' | xargs npx prettier --write"
          }
        ]
      }
    ]
  }
}

Tausche für andere Sprachen ruff format, gofmt -w oder rustfmt ein. Da der Formatter nach der Änderung läuft, sieht Claude die formatierte Datei beim nächsten Lesen, und du siehst im Review keine reinen Stil-Diffs mehr.

Beispiel 2: gefährliche Befehle und geschützte Dateien blockieren

Ein PreToolUse-Hook ist der Ort für harte Grenzen. Dieses Skript, angelehnt an den offiziellen Guide, blockiert Änderungen an sensiblen Dateien. Speichere es als .claude/hooks/protect-files.sh:

#!/bin/bash
INPUT=$(cat)
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')

PROTECTED_PATTERNS=(".env" "package-lock.json" ".git/")

for pattern in "${PROTECTED_PATTERNS[@]}"; do
  if [[ "$FILE_PATH" == *"$pattern"* ]]; then
    echo "Blocked: $FILE_PATH matches protected pattern '$pattern'" >&2
    exit 2
  fi
done

exit 0

Mach es mit chmod +x .claude/hooks/protect-files.sh ausführbar und registriere es dann:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/protect-files.sh"
          }
        ]
      }
    ]
  }
}

Dasselbe Muster funktioniert für Shell-Befehle. Matche Bash, lies .tool_input.command und beende mit 2, wenn der Befehl etwas enthält, das du nie willst, etwa rm -rf /, git push --force oder DROP TABLE. Bedenke, dass sich ein String-Check leicht umgehen lässt; sieh ihn also als Sicherheitsgurt, nicht als Schloss.

Beispiel 3: Desktop-Benachrichtigung bekommen

Lange Aufgaben halten öfter an und warten auf dich, als dir lieb ist. Ein Notification-Hook sagt dir sofort Bescheid. Unter macOS:

{
  "hooks": {
    "Notification": [
      {
        "matcher": "",
        "hooks": [
          {
            "type": "command",
            "command": "osascript -e 'display notification \"Claude Code needs your attention\" with title \"Claude Code\"'"
          }
        ]
      }
    ]
  }
}

Unter Linux nimmst du stattdessen notify-send 'Claude Code' 'Claude Code needs your attention'. Setze den Matcher auf permission_prompt, wenn dich nur Berechtigungsanfragen interessieren, oder füge einen Stop-Hook mit demselben Befehl hinzu, um zu hören, wann Claude fertig ist.

Weitere nützliche Muster

  • Kontext nach dem Komprimieren neu einspielen. Ein SessionStart-Hook mit dem Matcher compact kann per echo eine kurze Erinnerung an deine Regeln ausgeben. Seine stdout-Ausgabe wird dem frischen Kontext hinzugefügt.
  • Konfigurationsänderungen protokollieren. Ein ConfigChange-Hook kann jede Settings-Änderung an eine Logdatei anhängen.
  • Qualitätsschranke bei Stop. Ein Stop-Hook kann deine Tests ausführen und mit 2 und den Fehlern enden, sodass Claude weiter repariert. Prüfe das Feld stop_hook_active in der Eingabe, um eine Endlosschleife zu vermeiden. Claude Code übersteuert einen Stop-Hook außerdem nach mehreren Blockaden in Folge.
  • Einen sicheren Prompt automatisch freigeben. Ein PermissionRequest-Hook kann einen bestimmten Dialog mit {"behavior": "allow"} im Decision-Objekt beantworten. Halte den Matcher eng.

Sicherheitshinweise, bevor du Hooks hinzufügst

Hooks laufen als Shell-Befehle mit den Rechten deines Nutzers, ohne zu fragen. Das ist der Sinn der Sache, aber auch das Risiko.

  • Lies Hooks, bevor du einem Repo traust. Die .claude/settings.json eines Projekts kann Hooks mitbringen. Prüfe sie so, wie du ein postinstall-Skript prüfen würdest.
  • Setze Variablen in Anführungszeichen. Die Hook-Eingabe stammt vom Modell, und das Modell liest Dateien und Webseiten, die Prompt Injection enthalten können. Nutze "$VAR", werte Eingaben nie mit eval aus und bevorzuge jq gegenüber selbstgebautem Parsing.
  • Nutze absolute oder projektrelative Pfade mit $CLAUDE_PROJECT_DIR, damit ein Hook nicht vom aktuellen Ordner abhängt.
  • Halte Hooks schnell. Ein langsamer PreToolUse-Hook bremst jeden Tool-Aufruf. Setze ein timeout.
  • Hooks sind keine Sandbox. Ein Deny-Hook blockiert Aufrufe auch unter --dangerously-skip-permissions, was nützlich ist, aber ein entschlossener oder verwirrter Agent kann dasselbe Ergebnis auf anderem Weg erreichen. Kombiniere Hooks mit Deny-Regeln und Isolation, wie in Sicherheit von KI-Coding-Agenten und Berechtigungen sicher überspringen beschrieben.
  • Kenne die Ausschalter. "disableAllHooks": true schaltet Hooks ab, und Admins können allowManagedHooksOnly in den Managed Settings setzen, um Nutzer- und Projekt-Hooks zu blockieren.

Verhält sich ein Hook falsch, starte Claude Code mit claude --debug oder schreibe das Log mit --debug-file /tmp/claude.log in eine Datei, um Matcher, Exit-Code und Ausgabe jedes Hooks zu sehen.

So nutzt AgentVera Hooks

AgentVera ist eine Desktop-App, die Claude Code, Codex, Gemini CLI und andere Coding-CLIs nebeneinander ausführt. Hooks sind ein großer Teil davon, wie es mit Claude Code zusammenarbeitet.

  • Deine Hooks bleiben deine. AgentVera übergibt jedem Claude Code Prozess seine eigenen Hooks über das Flag --settings. Claude Code führt sie mit den Hooks in deinen Settings-Dateien zusammen, und deine settings.json wird nicht verändert.
  • Der Agent-Status kommt aus Hooks. Der Status, den jeder Agent in der App zeigt, wird aus den eigenen Hook-Events der CLI gelesen, nicht aus dem Terminaltext geraten.
  • Smart Tools sind Hooks. Die Funktion Smart Tools fügt lokalen Claude Code Agenten vier Hooks hinzu: Sie blockiert wiederholtes Lesen derselben Datei, führt nur die Tests aus, die eine Änderung betrifft, prüft direkt nach einer Änderung auf Fehler und teilt Ergebnisse zwischen Agenten. Sie sind standardmäßig an, und du kannst sie unter Settings → „Token saving“ ausschalten.
  • Kein Notify-Skript nötig. Benachrichtigungen und Freigaben sammeln jeden Agenten, der auf eine Freigabe wartet, in einer Warteschlange, senden Desktop-Benachrichtigungen, solange die App im Hintergrund ist, und können dein Handy über Telegram anpingen.

Auch Qwen Code bekommt die Hooks von AgentVera, weil sein Hook-System den Events und dem JSON-Format von Claude Code folgt.

Fazit

Starte mit einem PostToolUse-Formatter und einer PreToolUse-Sperre, prüfe sie mit /hooks und --debug und füge mehr erst hinzu, wenn eine Regel immer wieder gebrochen wird. Mit Exit 2 blockierst du, gib JSON aus, wenn du mehr Kontrolle brauchst, und behandle jeden Hook als Code, der mit deinen Rechten läuft.

Mehr dazu:

Um AgentVera auszuprobieren, beginne auf der Startseite.

Häufige Fragen

Was sind Claude Code Hooks?

Hooks sind Shell-Befehle, HTTP-Aufrufe, MCP-Tools oder Prompts, die Claude Code an festen Punkten einer Session selbstständig ausführt, etwa vor einem Tool-Aufruf, nach einer Dateiänderung oder wenn Claude deine Aufmerksamkeit braucht. Du definierst sie in settings.json.

Wohin kommen Claude Code Hooks?

In den Schlüssel hooks einer Settings-Datei: ~/.claude/settings.json für alle deine Projekte, .claude/settings.json für ein Projekt, das du über Git teilst, oder .claude/settings.local.json für ein Projekt nur auf deinem Rechner. Auch Plugins, Skills und Subagenten können Hooks definieren.

Wie blockiert ein Hook einen Tool-Aufruf?

Ein PreToolUse-Hook beendet sich entweder mit Code 2 und schreibt den Grund nach stderr, oder er beendet sich mit 0 und gibt JSON mit permissionDecision deny aus. Claude Code bricht den Aufruf ab und zeigt Claude den Grund. Exit-Code 1 blockiert nicht.

Laufen Hooks auch mit --dangerously-skip-permissions?

Ja. Ein Hook, der deny zurückgibt, blockiert den Tool-Aufruf auch im Modus bypassPermissions. Damit ist ein PreToolUse-Hook eine nützliche letzte Sicherung für Befehle, die du nie willst, aber keine Sandbox.

Wie debugge ich einen Claude Code Hook, der nicht feuert?

Öffne /hooks, um zu sehen, welche Hooks geladen sind und woher sie stammen, prüfe, ob der Matcher genau den Tool-Namen trifft, und starte Claude Code mit --debug oder --debug-file, um jeden Hook-Lauf samt Exit-Code und Ausgabe zu sehen.

Hol deine Agenten an einen Tisch.

Lade AgentVera kostenlos herunter; deine installierten CLIs sind sofort startklar.

Weitere Beiträge