Claude Code Hooks: Einrichtung und 7 fertige Beispiele
Markdown7 sofort einsatzbereite Hooks für .claude/settings.json zum Kopieren und Einfügen: Schutz für .env, prettier, Force-Push auf main, Gedankenstriche und ein Dev-Server. 4 Handler-Typen und 31 Ereignisse.
Vollständigen Artikel lesen
Hooks in Claude Code sind Shell-Befehle, HTTP-Aufrufe oder kurze LLM-Prüfungen, die automatisch vor oder nach einer Claude-Aktion ausgeführt werden. Sie ergänzen Prüfungen bestimmter Toolereignisse, formatieren Code nach Bearbeitung und liefern frischen Sitzungskontext. Ein passender PreToolUse-Hook kann diesen Aufruf ablehnen. Die Regex-Beispiele sind praktische Zusatzkontrollen, keine Sicherheitsgrenze sämtlicher Datei- und Git-Zugriffswege. Im Folgenden findest du eine Aufschlüsselung der 4 Handler-Typen und 31 Lifecycle-Ereignisse, einen Test für die Frage „Wann gehört eine Regel in einen Hook?“, 7 Hooks zum Kopieren für .claude/settings.json und 3 Fallstricke, die mir selbst begegnet sind.
Ailoot-Dokumentation: API, Schlüssel und Einrichtung.
Was sind Hooks in Claude Code?
Das Wichtigste
Ein Hook ist ein Shell-Befehl, ein HTTP-Aufruf, ein kurzer LLM-Prompt oder ein Subagent, den Claude Code als Reaktion auf ein bestimmtes Lifecycle-Ereignis ausführt. Anders als Anweisungen in CLAUDE.md, die das Modell ignorieren oder vergessen kann, wird ein Hook jedes Mal auf dieselbe Weise ausgeführt: Er ist Code, kein Vorschlag.
Ein halbes Jahr lang stand die Regel „.env niemals direkt bearbeiten“ in meiner CLAUDE.md. Claude verstieß etwa einmal im Monat dagegen. Nicht absichtlich; am Ende einer langen Sitzung hatte es die Regel einfach vergessen. Einmal landete ein zusätzliches Token in .env; ein anderes Mal überschrieb eine Variable mit einem doppelten Namen die verwendete Variable.
Ich habe diese Regel in einen Hook verschoben, sodass sie nicht mehr vergessen werden kann. Liefert ein passendes direktes Bearbeitungstool einen .env-Pfad, lehnt der konfigurierte Hook diesen Aufruf ab. Bash-Umleitung, Skripte, MCP und andere Wege brauchen eigene Kontrollen. Das Beispiel macht die Datei nicht unveränderlich.
Hooks sind Shell-Befehle, HTTP-Endpunkte oder LLM-Prompts, die du definierst und die automatisch an bestimmten Punkten im Lifecycle von Claude Code ausgeführt werden.
Anders gesagt: Ein Hook ist dein Befehl, den Claude Code bei einem bestimmten Ereignis ausführt. Jede Dateibearbeitung, jeder Bash-Start, jeder Sitzungsbeginn und jeder Abschluss eines Subagents ist ein Ereignis, an das du einen Hook knüpfen kannst.
Warum das für Vibe-Coder wichtig ist:
- Dateien mit Geheimnissen vor versehentlichen Änderungen schützen.
- prettier/eslint nach jeder Codegenerierung automatisch ausführen.
git push --forceauf main ohne ausdrückliche Erlaubnis blockieren.- Zu Beginn jeder Sitzung aktuellen Kontext einfügen.
- Claudes Aktionen zu Prüfzwecken protokollieren.
CLAUDE.md kümmert sich um den Kontext. Hooks kümmern sich um Aktionen. Das sind zwei verschiedene Dinge, und sie zu verwechseln ist der häufigste Anfängerfehler.
Worin unterscheiden sich Hooks von CLAUDE.md und Skills?
Das Wichtigste
CLAUDE.md ist Text, den das Modell zu Beginn einer Sitzung liest und zu befolgen versucht. Ein Hook ist ein Shell-Befehl, den der Claude-Code-Prozess unabhängig vom Modell als Reaktion auf ein Ereignis ausführt. Ein Skill ist ein benanntes Anweisungspaket, das Claude wie eine Funktion über das Skill-Tool aufrufen kann. Alle drei dienen unterschiedlichen Aufgabenarten.
Anthropics Dokumentation erklärt ausdrücklich den Unterschied zwischen CLAUDE.md und Hooks:
Der Inhalt von CLAUDE.md wird nach dem System-Prompt als Nutzernachricht übermittelt, nicht als Teil des System-Prompts. Claude liest ihn und versucht, ihn zu befolgen, aber eine strikte Einhaltung ist nicht garantiert.
Anders gesagt: Der Inhalt von CLAUDE.md erreicht Claude als erste Nutzernachricht, nicht als Systemanweisung. Das Modell liest ihn und versucht, ihn zu befolgen. Garantien gibt es jedoch nicht, besonders gegen Ende einer langen Sitzung, wenn der Kontext wächst und ältere Anweisungen verdrängt werden.
Ein Hook funktioniert anders. Er ist in settings.json abgelegt, hängt nicht vom Modell ab und wird vom Claude-Code-Prozess bei einem bestimmten Ereignis ausgeführt: PreToolUse, PostToolUse, SessionStart und weitere. Der Befehl läuft, bevor Claude das Ergebnis erhält, und kann eine Aktion physisch blockieren (exit 2) oder nach der Bearbeitung eine Datei ändern (exit 0).
Vergleich:
| Merkmal | CLAUDE.md | Hook | Skill |
|---|---|---|---|
| Speicherort | CLAUDE.md im Projektstammverzeichnis | .claude/settings.json | Ordner .claude/skills/ |
| Wer führt es aus? | Das Modell liest es als Teil einer Nutzernachricht | Der Claude-Code-Prozess bei einem Ereignis | Das Modell über das Skill-Tool |
| Ausführung garantiert? | Nein (Best-Effort) | Ein konfigurierter passender Command-Hook führt Code aus; Fehler- und Exitverhalten hängen vom Ereignis ab | Über einen Modellaufruf (mit Berechtigung, aber nicht auf Shell-Ebene) |
| Zweck | Projektkontext, Ton, Stil, Routing | Einschränkungen, Formatierung, Prüfungen bestimmter Dateien | Komplexe mehrstufige Abläufe mit Parametern |
| Wann wird es ausgeführt? | Bei jeder Modellnachricht in einer Sitzung | Bei einem Lifecycle-Ereignis | Wenn das Modell es aufruft |
| Kann es eine Aktion blockieren? | Nein | Ja (exit 2) | Nein |
Die vollständige CLAUDE.md-Vorlage und 6 Regeln für eine lebendige Datei findest du in der Anleitung So konfigurierst du CLAUDE.md im Jahr 2026: eine sofort einsatzbereite Vorlage und 6 Regeln. Mehr über Skills als benannte Fähigkeiten erfährst du in Claude Code Skills: So baust du deine eigene Bibliothek.
Ein Hook ist die niedrigste Ebene: kein Modell, keine Interpretation, nur Shell.
Hooks sind eines der Werkzeuge des Context Engineerings, neben Plan Mode und /compact.
4 Hook-Typen: Command, HTTP, Prompt, Agent – welchen solltest du wählen?
Das Wichtigste
Claude Code hat 4 Handler-Typen. Command ist ein Shell-Skript und der häufigste Typ. HTTP sendet einen POST-Aufruf an deine URL und eignet sich für Teams und Integrationen. Prompt führt einen kurzen Haiku-Aufruf für eine semantische Prüfung aus. Agent erstellt einen Subagent mit Werkzeugzugriff für gründliche Validierungen. Anfänger brauchen Command und Prompt; damit lassen sich 90% der Aufgaben abdecken.
| Typ | Was er tut | Wann du ihn wählen solltest | Komplexität |
|---|---|---|---|
| Command | Führt einen Shell-Befehl aus, empfängt JSON über stdin und antwortet mit einem Exitcode und stdout | 90% der Fälle: Pfade prüfen, formatieren, Inhalte mit grep durchsuchen, lsof, Git-Operationen | niedrig |
| HTTP | Sendet einen POST-Aufruf mit einem JSON-Body an deine URL und empfängt JSON als Antwort | Wenn eine Regel zu einem Team/einer CI gehört: zentrale Validierung, gemeinsames Status-Dashboard, Remote-Auditing | mittel (erfordert einen Server) |
| Prompt | Sendet einen kurzen Prompt an Haiku (standardmäßig) und erhält für eine semantische Bewertung ja/nein | Wenn grep nicht ausreicht: „Bewerte, ob dieser Text nach Unternehmen klingt“ | niedrig |
| Agent | Erstellt einen Subagent mit Read/Grep/Glob-Zugriff für mehrstufige Prüfungen | Aufwendige Validierung: „Finde alle geänderten Dateien und prüfe, ob entsprechende Tests existieren“ | hoch |
Der häufigste Fehler ist, mit Command etwas erledigen zu wollen, wofür Prompt gedacht ist. „Keine Gedankenstriche in .md-Dateien“ lässt sich zum Beispiel mit grep prüfen, daher ist Command ideal. „Nicht im Unternehmenston schreiben“ ist dagegen eine semantische Vorgabe und braucht deshalb Prompt.
Wenn du gerade anfängst, verwende Command. Die 7 sofort einsatzbereiten Hooks unten sind alle vom Typ Command. HTTP/Prompt/Agent kommen später, wenn du so weit bist.
Wo befindet sich settings.json, und welche 4 Konfigurationsebenen gibt es?
Das Wichtigste
Claude Code sucht an 4 Stellen nach Konfiguration: Enterprise (über MDM bereitgestellt), Nutzer (~/.claude/settings.json), Projekt (.claude/settings.json im Repository-Stammverzeichnis) und lokal (.claude/settings.local.json, nicht in Git). Alle 4 Dateien werden zu Sitzungsbeginn zusammengeführt, wobei die niedrigere Ebene Vorrang hat.
| Ebene | Pfad | Geltungsbereich | In Git? |
|---|---|---|---|
| Enterprise | /Library/Application Support/ClaudeCode/settings.json (Mac), /etc/claude-code/settings.json (Linux) | Teamweit über MDM | nein |
| Nutzer | ~/.claude/settings.json | Alle Projekte eines Nutzers | nein |
| Projekt | .claude/settings.json im Repository-Stammverzeichnis | Dieses Projekt, für das ganze Team | ja |
| Lokal | .claude/settings.local.json | Nur dieses Projekt, nur dieser Nutzer | nein (in .gitignore) |
Ich lege die 7 unten beschriebenen Hooks in der Projektdatei .claude/settings.json ab. So werden sie beim Klonen des Repositorys automatisch übernommen und Claude befolgt dieselben Regeln in einem neuen Fenster. Persönliche experimentelle Hooks kommen in .claude/settings.local.json, damit sie dem Team nicht in die Quere kommen.
Alle 4 Dateien werden zu Sitzungsbeginn zusammengeführt. Wenn Regeln miteinander in Konflikt stehen, hat die niedrigere Ebene Vorrang (lokal vor Projekt, Projekt vor Nutzer).
Am schnellsten erstellst du deinen ersten Hook über das interaktive /hooks-Menü direkt in Claude Code. Wähle ein Ereignis aus der Liste, einen Matcher (zum Beispiel Bash oder Edit) und füge einen Befehl ein. Claude Code schreibt gültiges JSON in settings.json.
31 Lifecycle-Ereignisse: Welche davon brauchen Anfänger?
Das Wichtigste
Claude Code hat derzeit 31 Lifecycle-Ereignisse, die sich auf 3 Häufigkeiten verteilen: einmal pro Sitzung (SessionStart/SessionEnd), einmal pro Turn (UserPromptSubmit/Stop) und bei jedem Tool-Aufruf (PreToolUse/PostToolUse). Anfänger brauchen 4 der 31 Ereignisse: PreToolUse für Einschränkungen, PostToolUse für automatische Änderungen, SessionStart für Kontext und Stop für Abschlussprüfungen.
Ereignisse treten in drei Häufigkeiten auf: einmal pro Sitzung (SessionStart, SessionEnd), einmal pro Turn (UserPromptSubmit, Stop, StopFailure) und bei jedem Tool-Aufruf innerhalb der agentischen Schleife (PreToolUse, PostToolUse).
Es ist wichtig, die drei Häufigkeiten zu verstehen, damit du nicht durcheinanderkommst:
- Einmal pro Sitzung -
SessionStart,SessionEnd,Setup. Diese werden einmal ausgeführt, wenn Claude Code gestartet und beendet wird. Verwende sie, um Kontext zu initialisieren und temporäre Dateien aufzuräumen. - Einmal pro Turn -
UserPromptSubmit,UserPromptExpansion,Stop,StopFailure. Diese werden bei jeder deiner Nachrichten und jedes Mal ausgelöst, wenn das Modell stoppt. Verwende sie, um die Unterhaltung zu protokollieren und abschließende Ergebnisprüfungen durchzuführen. - Bei jedem Tool-Aufruf -
PreToolUse,PostToolUse,PostToolUseFailure,PermissionRequest,PermissionDenied,PostToolBatch,SubagentStart,SubagentStop. Diese werden bei jeder Dateibearbeitung, jedem Bash-Start, jeder Lese- oder Suchaktion ausgelöst. Das ist die am stärksten beanspruchte Ebene; hier gehören Einschränkungen, Formatierung und Validierungen hin.
Die vollständige Liste der 31 Ereignisse enthält asynchrone/reaktive Ereignisse: TeammateIdle, PreCompact, PostCompact, Elicitation, ElicitationResult, InstructionsLoaded, ConfigChange, CwdChanged, FileChanged, WorktreeCreate, WorktreeRemove, Notification. Diese sind für fortgeschrittene Szenarien gedacht; überspringe sie zunächst.
Fünf-Fragen-Test: Wann gehört eine Regel in einen Hook, und wann bleibt sie in CLAUDE.md?
Das Wichtigste
Wenn es bei einer Regel um eine Aktion (nicht um Kontext) geht, sie Sicherheit/Geld/Daten betrifft, sich mit grep/regex prüfen lässt, schwer rückgängig zu machen ist und Claude bereits mindestens einmal dagegen verstoßen hat, gehört sie in einen Hook. Wenn weniger als drei dieser fünf Antworten „Ja“ lauten, bleibt sie in CLAUDE.md. Ich habe den Test auf meine 80 Zeilen angewandt und 5 Regeln in Hooks verschoben.
Für mich ist das ein Gesetz, kein Vorschlag. Ich habe die Entscheidung zwischen CLAUDE.md und einem Hook auf einen einfachen Fünf-Fragen-Test heruntergebrochen. Wenn mindestens drei Antworten „Ja“ lauten, gehört die Regel in einen Hook. Bei weniger als drei bleibt sie im Text.
Der Test „Wann gehört eine Regel in einen Hook?“:
- Geht es bei dieser Regel um eine Aktion statt um Kontext? („
rm -rfnicht ausführen“ ist eine Aktion; „Ich habe ein TypeScript-Projekt“ ist Kontext.) - Geht es bei dieser Regel um Sicherheit, Geld oder Daten? („
.envnicht anfassen“, „Schlüssel nicht veröffentlichen“.) - Kannst du sie mit grep, regex oder dem Exitcode eines Shell-Befehls prüfen? („Nach der Bearbeitung von
.mdprüfen, dass kein Gedankenstrich vorkommt.“) - Wären die Folgen schmerzhaft rückgängig zu machen, wenn die Regel verletzt würde? („Force-Push auf main“ ist schwer rückgängig zu machen; „Punkt am Satzende vergessen“ ist einfach.)
- Hast du bereits erlebt, dass Claude diese Regel mindestens einmal vergessen hat?
Beispiel: „Keine Gedankenstriche verwenden, nur Bindestriche.“ Aktion? Ja. Sicherheit? Nein. Mit grep prüfbar? Ja. Schwer rückgängig zu machen? Nein. Schon einmal verletzt? Ja. 3 von 5: in einen Hook damit.
Gegenbeispiel: „Ich habe Vibe-Coder, keine Entwickler; schreibe ohne Slang.“ Aktion? Eher Kontext. Sicherheit? Nein. Mit einem Regex prüfbar? Zu schwierig. Schwer rückgängig zu machen? Nein. Schon einmal verletzt? Ja. 1 von 5: in CLAUDE.md lassen.
Fünf meiner Regeln haben den Test bestanden. Sie sind in Hooks gewandert. Die übrigen 75% blieben in der Textdatei.
7 Hooks zum Kopieren
Das Wichtigste
Hier sind 7 sofort einsatzbereite Hooks für .claude/settings.json: Bearbeitungen an .env blockieren, prettier automatisch ausführen, Force-Pushes auf main verhindern, auf Gedankenstriche prüfen, vor npm run dev einen Port freigeben, nach erfolgreichen Tests automatisch committen und zu Sitzungsbeginn aktuellen Kontext einfügen. Kopiere jeden Hook in die Datei, starte Claude Code neu, und er funktioniert.
Alle 7 Hooks sind in einer einzigen .claude/settings.json-Datei im Projektstammverzeichnis gespeichert. Jeder Eintrag enthält einen Ereignistyp, einen Tool-Filter (Matcher) und einen Shell-Befehl. Exitcode 2 lehnt einen passenden PreToolUse-Aufruf vor Ausführung ab. Bei PostToolUse ist die Aktion bereits erfolgt. Der Hook liefert Rückmeldung und kann sie nicht rückgängig machen.
Bei den meisten Hook-Ereignissen blockiert nur Exitcode 2 eine Aktion. Claude Code behandelt Exitcode 1 als nicht blockierenden Fehler und setzt die Aktion fort, obwohl 1 unter Unix üblicherweise für einen Fehler steht.
exit 0 bedeutet Erfolg. exit 1 bedeutet einen Fehler, aber Claude macht weiter (das ist das erste Antimuster weiter unten). exit 2 wirkt ereignisabhängig: Es kann PreToolUse blockieren; PostToolUse meldet nur eine Rückmeldung zu einer bereits erfolgten Aktion.
Hook 1. Direkte .env-Bearbeitungen prüfen (PreToolUse, exit 2)
Blockiert Versuche, eine Datei direkt zu bearbeiten, die auf .env, .env.local, .env.production und Ähnliches passt.
{
"hooks": {
"PreToolUse": [
{
"matcher": "Edit|Write|MultiEdit",
"hooks": [
{
"type": "command",
"command": "FILE=$(jq -r '.tool_input.file_path // empty'); [ -z \"$FILE\" ] && exit 0; echo \"$FILE\" | grep -qE '(^|/)\\.env(\\.|$)' && { echo 'Do not edit `.env` directly. Tell me which variable to add, and I’ll open the file myself.' >&2; exit 2; } || exit 0"
}
]
}
]
}
}
Was der Hook tut: Vor jedem Edit/Write/MultiEdit ermittelt er den Dateipfad, prüft mit grep, ob dieser dem .env-Muster entspricht, und gibt in diesem Fall Exitcode 2 mit einer Meldung zurück. Claude sieht stderr und sagt mir: „Ich habe versucht, .env zu bearbeiten, aber der Hook hat mich blockiert; sag mir, welche Variable ich hinzufügen soll.“
Hook 2. prettier nach jeder Codebearbeitung automatisch ausführen (PostToolUse, exit 0)
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write|MultiEdit",
"hooks": [
{
"type": "command",
"command": "FILE=$(jq -r '.tool_input.file_path // empty'); [ -z \"$FILE\" ] && exit 0; echo \"$FILE\" | grep -qE '\\.(ts|tsx|js|jsx|json)$' && npx prettier --write \"$FILE\" 2>/dev/null & exit 0"
}
]
}
]
}
}
Was der Hook tut: Nach jedem Edit/Write ermittelt er den Pfad und führt prettier im Hintergrund mit & aus, wenn die Erweiterung .ts/.tsx/.js/.jsx/.json lautet. Er beendet sich immer mit Exitcode 0, denn hier geht es um Formatierung, nicht um eine Einschränkung. Wenn prettier fehlschlägt, ist das kein großes Problem; die Datei bleibt, wie sie war.
Der Unterschied zu Hook 1 ist grundlegend: PreToolUse + exit 2 ist eine harte Blockierung vor der Aktion. PostToolUse + exit 0 ist eine sanfte Änderung nach der Aktion. Das sind zwei verschiedene Modi für unterschiedliche Aufgaben.
Hook 3. git push --force auf den Branch main blockieren (PreToolUse, exit 2)
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "CMD=$(jq -r '.tool_input.command // empty'); [ -z \"$CMD\" ] && exit 0; echo \"$CMD\" | grep -qE 'git[[:space:]]+push' && echo \"$CMD\" | grep -qE '(\\-\\-force|\\-\\-force-with-lease|[[:space:]]-f([[:space:]]|$))' && echo \"$CMD\" | grep -qE '([[:space:]/:])main([[:space:]/:]|$)' && { echo 'Force push to main is forbidden. Create a new branch or ask me to do it manually.' >&2; exit 2; } || exit 0"
}
]
}
]
}
}
Was der Hook tut: Bei jedem Bash-Start prüft er, ob der Befehl push, --force/--force-with-lease/-f und main enthält. Wenn alle drei Bedingungen erfüllt sind, blockiert er mit einer klaren Meldung.
Früher stand diese Regel in Großbuchstaben in meiner CLAUDE.md. Claude verstieß trotzdem etwa alle zwei Wochen dagegen, wenn ich es bat, „einen Force-Push auszuführen, um meinen schlechten Commit rückgängig zu machen“. Der Hook erfasst die gezeigten wörtlichen Befehlsformen. Aliase, Skripte, impliziter Zielbranch oder anderer Git-Client können den Texttest umgehen. Erzwinge Main-Branchschutz auf dem Remote und beschränke verfügbare Zugangsdaten.
Hook 4. Gedankenstriche in Textdateien blockieren (PostToolUse, exit 2)
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write|MultiEdit",
"hooks": [
{
"type": "command",
"command": "FILE=$(jq -r '.tool_input.file_path // empty'); [ -z \"$FILE\" ] && exit 0; echo \"$FILE\" | grep -qE '\\.(md|mdx|tsx?)$' && grep -q $'\\xe2\\x80\\x94' \"$FILE\" && { echo \"File $FILE contains an em dash (U+2014). Replace it with a regular hyphen (U+002D).\" >&2; exit 2; } || exit 0"
}
]
}
]
}
}
Was der Hook tut: PostToolUse mit exit 2 meldet Claude nach der Bearbeitung einen Prüfungsfehler. Es verhindert die Schreibaktion nicht und nimmt sie nicht zurück. Wenn Claude eine Datei bearbeitet und einen Gedankenstrich darin stehen lässt, schlägt der Hook fehl, Claude erhält Rückmeldung und versucht es erneut. Nach 2–3 Sitzungen hört Claude ganz auf, dieses Zeichen einzufügen.
Beachte, dass grep gezielt nach dem Gedankenstrich-Zeichen (U+2014) sucht, und zwar mit $'\xe2\x80\x94', der Bash-Notation für seine UTF-8-Bytes. Wenn du einfach grep -q '-' schreibst, erfasst das auch normale Bindestriche und wird bei jeder Datei ausgeführt. Ein unsichtbares Zeichen bringt die ganze Logik durcheinander.
Hook 5. Port 3000 vor npm run dev freigeben (PreToolUse, exit 0)
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "CMD=$(jq -r '.tool_input.command // empty'); [ -z \"$CMD\" ] && exit 0; echo \"$CMD\" | grep -qE '(npm|pnpm|yarn)[[:space:]]+run[[:space:]]+dev' && { lsof -ti:3000 | xargs -r kill -9 2>/dev/null; echo 'Port 3000 freed before launch' >&2; }; exit 0"
}
]
}
]
}
}
Was der Hook tut: Vor jedem npm run dev (oder pnpm/yarn) beendet er den Prozess auf Port 3000. Er gibt immer Exitcode 0 zurück, denn es geht um Vorbereitung, nicht um eine Einschränkung. So werden Fallen einer bestimmten Art vermieden: Claude sieht „port in use“, hält das für einen Codefehler und beginnt, Dinge zu reparieren, obwohl der alte Server einfach noch lief.
Hook 6. Nach erfolgreichen Tests automatisch committen (PostToolUse, exit 0)
{
"hooks": {
"PostToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "INPUT=$(cat); CMD=$(printf '%s' \"$INPUT\" | jq -r '.tool_input.command // empty') || exit 1; EXIT=$(printf '%s' \"$INPUT\" | jq -r '.tool_response.exit_code // empty') || exit 1; case \"$CMD\" in 'npm test'|'pnpm test'|'yarn test') ;; *) exit 0 ;; esac; [ \"$EXIT\" = \"0\" ] || exit 0; git diff --cached --quiet || { echo 'Existing staged changes: review manually' >&2; exit 0; }; git add -- src/orders.ts tests/orders.test.ts || exit 1; git diff --cached --quiet && exit 0; git commit -m 'tests: checkpoint after successful run'"
}
]
}
]
}
}
Was der Hook tut: Er liest JSON-stdin einmal und akzeptiert nur die gezeigten einfachen exakten Testbefehle. Er macht nur bei ausdrücklich null gesetztem tool_response.exit_code weiter. Fehlt das Feld in deiner Version, überspringt er den Commit statt Erfolg zu raten. Ersetze src/orders.ts und tests/orders.test.ts durch genau aufgabeneigene Dateien. Bereits vorgemerkte Änderungen stoppen das Rezept gegen fremde Inhalte. Fehlgeschlagenes Vormerken stoppt, leerer Diff erzeugt keinen Commit und normale Commitprüfungen bleiben aktiv. Teste Erfolgs-, Fehler- und fehlende Ergebniseingaben offline vor der Aktivierung.
Antimuster: git commit --no-verify bei jedem Edit. Lasse Commitprüfungen aktiviert. Löst eine Prüfung diese Automatisierung rekursiv aus, grenze den Auslöser ein oder ändere das Design. Umgehe Verifikation nicht standardmäßig. Prüfe vorgemerkten Diff und Aufgabenbesitz vor automatischen Commits.
Hook 7. Zu Sitzungsbeginn aktuellen Kontext einfügen (SessionStart mit additionalContext)
{
"hooks": {
"SessionStart": [
{
"matcher": "*",
"hooks": [
{
"type": "command",
"command": "BRANCH=$(git symbolic-ref --short HEAD 2>/dev/null || echo 'no-branch'); STATUS=$(git status --short 2>/dev/null | head -10); echo \"{\\\"additionalContext\\\":\\\"Current branch: $BRANCH. Uncommitted changes:\\n$STATUS\\\"}\""
}
]
}
]
}
}
Was der Hook tut: Zu Sitzungsbeginn ermittelt er den aktuellen Git-Branch und die Liste der nicht übernommenen Änderungen und gibt dann JSON mit einem Feld additionalContext zurück. Claude Code fügt diesen Text in die erste Systemnachricht ein, sodass Claude von Anfang an weiß, wo es sich befindet und was aus der vorherigen Sitzung noch offen ist.
Verwende additionalContext für Informationen, die Claude über den aktuellen Zustand der Umgebung oder einen gerade abgeschlossenen Vorgang kennen sollte: Umgebungszustand (aktueller Branch, Deployment-Ziel, aktive Feature-Flags), bedingte Projektregeln (welcher Testbefehl für die gerade bearbeitete Datei gilt), externe Daten (dir zugewiesene offene Issues, aktuelle CI-Ergebnisse).
Mögliche Erweiterungen dieses Hooks: den letzten Commit und seine Beschreibung, den Status von GitHub Actions, den aktiven Sprint aus Linear/Jira und offene Issues für das aktuelle Repository abrufen.
3 Hook-Fallstricke, auf die fast jeder Zweite hereinfällt
Das Wichtigste
Der erste Fallstrick: Ein Hook mit exit 1 wird stillschweigend durchgelassen, und Claude ignoriert den Fehler. Der zweite: Aufwendige Arbeit in einem Hook wird nach einem Timeout von 60 Sekunden beendet. Der dritte: Ein Hook sieht nur die Umgebungsvariablen aus ~/.zshrc, nicht die .env des Projekts. Jeder lässt sich mit einer Zeile beheben; kennt man diese Fallstricke nicht, kann es so wirken, als würden „Hooks nicht funktionieren“.
Fallstrick 1. Ein Hook mit exit 1 wird stillschweigend durchgelassen
Der gefährlichste Fall. Eine Woche lang dachte ich, mein .env-Schutz funktioniere. Tatsächlich hatte ich im Hook jq -r '.tool_input.file_path' verwendet, ohne auf null zu prüfen. Wenn tool_input leer war, beendete jq sich mit Code 1. Claude behandelte exit 1 als „Hook-Fehler, weitermachen“, und die Aktion wurde ausgeführt. Ich konnte nicht verstehen, warum .env trotzdem angefasst wurde. Die Ursache erkannte ich erst, als ich den Hook mit leerer Eingabe manuell in einem Terminal ausführte.
Die Lösung ist eine Regel: Jeder Hook sollte mit einer Prüfung auf null beginnen und mit || exit 0 enden. Jeder unerwartete Exit führt dann zu einem „sicheren“ Exit, statt stillschweigend durchgelassen zu werden.
# Safe start and end template:
FILE=$(jq -r '.tool_input.file_path // empty')
[ -z "$FILE" ] && exit 0
# ... your logic ...
exit 0 # or || exit 0 on one line
Fallstrick 2. Langsame Hooks werden durch einen Timeout beendet
Ich hatte einen Hook, der nach Edit eslint --fix für die ganze Datei ausführte. Große Dateien (1000+ Zeilen) brauchten 8–10 Sekunden. Das Standard-Timeout für Hooks in Claude Code beträgt 60 Sekunden, also dachte ich, ich läge innerhalb des Limits. Wenn aber innerhalb einer Minute mehrere Edits erfolgen, sammeln sich die Hooks an und bilden eine Warteschlange, und manche werden einfach übersprungen. Das bemerkte ich erst, als im Log „hook timed out“ stand.
Lösung: Wenn ein Hook aufwendige Arbeit erfordert, führe sie im Hintergrund aus. eslint --fix "$FILE" &, ohne auf das Ergebnis zu warten. Dann beendet sich der Hook in Millisekunden mit Exitcode 0, während die eigentliche Arbeit im Hintergrund läuft.
# Heavy operation in background:
npx eslint --fix "$FILE" &
exit 0
Fallstrick 3. Ein Hook kann Umgebungsvariablen des Projekts nicht sehen
Ich dachte, process.env sei in einem Hook genauso verfügbar wie in einer Node.js-App. Ist es nicht. Der Hook läuft in einer vom übergeordneten Claude-Code-Prozess gestarteten Shell und sieht nur, was in ~/.zshrc oder ~/.bashrc steht, sowie Variablen, die Claude Code ausdrücklich übergibt. Wenn dein Hook OPENAI_API_KEY aus .env braucht, musst du .env manuell auslesen.
Lösung: Lies .env ausdrücklich im Hook-Befehl aus.
# Passing project env into hook:
cd "$PWD" && source .env && curl -H "Authorization: Bearer $OPENAI_API_KEY" ...
# Or with dotenv:
dotenv -e .env -- node my-validator.js
Das ist besonders mühsam in einem Monorepo, in dem verschiedene Pakete eigene .env-Dateien haben. Löse es, indem du PWD durchreichst und zu Beginn des Befehls ausdrücklich cd "$PWD" ausführst.
Wie testest du Hooks vor dem Commit?
Das Wichtigste
Ein Hook ist ein Shell-Befehl. Du kannst ihn manuell in einem Terminal mit künstlicher stdin-Eingabe ausführen, seinen Exitcode und stderr prüfen und das alles erledigen, bevor Claude Code ihn ausführt. Das spart stundenlanges Debugging und schützt dich vor Hooks, die ihre eigene Funktion blockieren.
Einfaches Testszenario. Nimm den Hook-Befehl aus dem JSON, öffne ein Terminal und übergib ihm das JSON, das Claude Code senden würde:
# Test hook 1 (protect .env):
echo '{"tool_input":{"file_path":".env"}}' | bash -c '
FILE=$(jq -r ".tool_input.file_path // empty")
[ -z "$FILE" ] && exit 0
echo "$FILE" | grep -qE "(^|/)\.env(\.|$)" && {
echo "BLOCKED" >&2
exit 2
} || exit 0
'
echo "Exit code: $?"
Du solltest BLOCKED in stderr und Exit code: 2 sehen. Wenn du Exitcode 0 erhältst, funktioniert dein Regex nicht; korrigiere grep.
Zu prüfende Szenarien:
- Erwarteter Pfad - die Datei sollte blockiert werden.
.envfür Hook 1,git push --force mainfür Hook 3. - Negativer Pfad - die Datei sollte nicht blockiert werden.
README.mdfür Hook 1,git push origin feature-branchfür Hook 3. - Grenzfälle - leere Eingabe, Nullwerte, unerwartete Zeichen. Der Hook sollte entweder Exitcode 0 zurückgeben oder mit einem klaren Fehler abbrechen, statt stillschweigend durchzulassen.
Wohin mit experimentellen Hooks? Nicht in .claude/settings.json, sondern in .claude/settings.local.json (nicht in Git). Lege dort neue Hooks unter PostToolUse mit echo statt der echten Aktion ab, prüfe, ob sie ausgelöst werden, und verschiebe sie anschließend in die Produktionsdatei settings.json.
// Experiment in settings.local.json - writes to /tmp/hook-debug.log:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit",
"hooks": [
{
"type": "command",
"command": "echo \"[hook fired] $(date) $(jq -r '.tool_input.file_path // \"?\"')\" >> /tmp/hook-debug.log"
}
]
}
]
}
}
Nach 5–10 Bearbeitungen in Claude Code öffnest du /tmp/hook-debug.log und siehst, welche Dateien den Hook ausgelöst haben. Wenn die Datei leer ist, passt der Matcher nicht; korrigiere den Regex.
Wie geht es weiter?
Das Wichtigste
Beginne mit PostToolUse-Hooks, die exit 0 zurückgeben; sie sind am sichersten und blockieren nichts. Füge nach einer Woche deinen ersten PreToolUse-Hook mit exit 2 hinzu, um .env zu schützen oder Force-Pushes zu verhindern. Nach zwei Wochen schrumpft CLAUDE.md von einem Vertrag mit 200 Zeilen auf eine kurze Kontextbeschreibung mit 50–80 Zeilen, während ausgewählte Ereignisprüfungen in Hooks und harte Zugriffsgrenzen separat in Berechtigungen, Sandbox und Remoteschutz liegen.
5 Schritte für die kommende Woche:
- Durchsuche deine
CLAUDE.mdnach Wörtern wie „niemals“, „verboten“, „immer“ und „muss“. Jede solche Regel kommt als Hook infrage. Wenn eine Regel eine Handlungsanweisung ist, wird die Textdatei sie nicht durchsetzen. - Beginne mit PostToolUse + exit 0. Das ist am sichersten: Die Hooks nehmen eine Änderung vor, blockieren aber nichts. prettier (Hook 2), den Dev-Server beenden (Hook 5), nach Tests automatisch committen (Hook 6).
- Füge deinen ersten PreToolUse + exit 2 erst hinzu, wenn du sicher bist. Schütze zum Beispiel
.env(Hook 1) oder verhindere Force-Pushes (Hook 3). Das ist eine harte Blockierung, und ein Fehler im grep-Befehl kann dich selbst blockieren. - Teste Hooks vor dem Commit manuell (siehe Abschnitt oben). Das spart stundenlanges Debugging.
- Verwende
settings.local.jsonfür Experimente. Lege dort neue Hooks mit echo statt der echten Aktion ab.
Sobald deine Hooks eingerichtet sind, ist der nächste Schritt in diesem Themencluster Änderungen in Claude Code rückgängig machen, falls ein Hook etwas Gefährliches durchlässt. Wenn Claude sich zwischen Sitzungen an die Projektstruktur erinnern soll, erstelle ein Second Brain in Claude Code (Ordner business/ + verknüpfte Wissensdatenbank). Und wenn du den Skills deines Projekts Namen geben möchtest, damit sie wiederverwendet werden können, lies Claude Code Skills.
Quellen
Wenn du über Ailoot auf neuronale Netze zugreifen möchtest, sieh dir Ailoot an.
Weitere Details
Fragen und Antworten
Warum muss ein Hook seine JSON-Eingabe nur einmal lesen?
Stdin ist ein Stream. Der erste jq-Prozess verbraucht ihn; spätere Lesezugriffe sehen EOF. Speichere die Eingabe einmal und extrahiere Befehl und Ergebnis daraus. Behandle fehlendes oder unbekanntes Ergebnis als ungeprüft und überspringe den Auto-Commit.
Erzeugen die Beispiel-Regex-Hooks eine Sicherheitsgrenze?
Nein. Sie prüfen bestimmte Ereignisse und erkennbare Befehlstexte. Shellumleitung, Aliase, Skripte, MCP und andere Schreibwege können sie umgehen. Harte Grenzen brauchen eingeschränkte Zugangsdaten, Dateirechte, Sandbox und geschützte Branches.