Deterministische Kontrolle statt hoffnungsvoller Prompt-Instruktion
Hooks in Claude Code sind Shell-Befehle, die bei genau definierten Ereignissen wie einem Werkzeugaufruf automatisch ausgefuehrt werden, unabhaengig davon, ob der Modell-Kontext gerade an eine CLAUDE.md-Regel erinnert oder nicht. Waehrend eine Prompt-Instruktion nur eine Wahrscheinlichkeit erhoeht, erzwingt ein Hook ein Verhalten deterministisch, auf Betriebssystemebene, nicht auf Sprachmodell-Ebene.
Inhaltsverzeichnis
- 1. Welches Problem Hooks loesen
- 2. Die Hook-Events im Ueberblick
- 3. Hooks konfigurieren: settings.json und Matcher
- 4. Ein PreToolUse-Hook fuer Sicherheitsprueffungen
- 5. Ein PostToolUse-Hook fuer automatisches Formatieren
- 6. Hook-Input und Exit-Codes verstehen
- 7. Hooks versus CLAUDE.md: deterministisch statt Vorschlag
- 8. Sicherheitsaspekte und typische Fallstricke
- 9. Hook-Events im direkten Vergleich
- 10. Zusammenfassung
- 11. FAQ
1. Welches Problem Hooks loesen
Ein Hook in Claude Code ist ein Shell-Befehl, der von der CLI selbst, nicht vom Sprachmodell, bei einem festgelegten Zeitpunkt im Ablauf ausgefuehrt wird. Das loest ein grundlegendes Problem von Prompt-basierten Anweisungen: Eine Regel in CLAUDE.md wie "fuehre nach jeder Aenderung den Formatter aus" ist eine Instruktion an ein Sprachmodell, das sie mit hoher, aber nicht garantierter Wahrscheinlichkeit befolgt. Ein Hook dagegen wird vom Betriebssystem ausgefuehrt, unabhaengig davon, ob das Modell gerade an diese Regel gedacht hat oder nicht.
Der Unterschied wird besonders bei sicherheitsrelevanten Regeln sichtbar. Ein Prompt-Hinweis wie "fuehre niemals rm -rf aus" verringert das Risiko, verhindert es aber nicht garantiert, weil ein Sprachmodell in seltenen Faellen von seinen eigenen Instruktionen abweicht. Ein Hook, der jeden Bash-Aufruf vor der Ausfuehrung prueft und bei einem Treffer auf ein gefaehrliches Muster den Exit-Code 2 zurueckgibt, blockiert die Ausfuehrung zuverlaessig, unabhaengig vom Modellverhalten. Diese Verschiebung von Wahrscheinlichkeit zu Determinismus ist der zentrale Wert des Hook-Systems.
2. Die Hook-Events im Ueberblick
Claude Code stellt mehrere Hook-Events an unterschiedlichen Punkten im Ablauf bereit. PreToolUse feuert, bevor ein Werkzeug wie Bash, Edit oder Write ausgefuehrt wird, und kann die Ausfuehrung noch verhindern. PostToolUse feuert direkt danach und eignet sich fuer Nacharbeiten wie automatisches Formatieren oder Linting. UserPromptSubmit feuert, sobald ein Nutzer eine Eingabe absendet, noch bevor Claude sie verarbeitet, und kann zusaetzlichen Kontext injizieren oder die Eingabe komplett blockieren.
Weitere Events runden das Bild ab: Stop feuert, wenn der Hauptagent seine Antwort beendet, SubagentStop analog fuer einen Subagenten, Notification bei Systembenachrichtigungen, und SessionStart beim Start einer neuen Session, ideal um Umgebungsinformationen einmalig zu laden. Jedes dieser Hook-Events erhaelt strukturierte JSON-Daten ueber stdin, etwa den Namen des aufgerufenen Werkzeugs und dessen Parameter bei PreToolUse, und kommuniziert seine Entscheidung ueber den Exit-Code und optional strukturierte JSON-Ausgabe zurueck an Claude Code.
#!/usr/bin/env bash
# .claude/hooks/load-env-info.sh -- SessionStart hook, runs once per session
set -euo pipefail
echo "--- Environment info loaded at session start ---"
echo "PHP version: $(php -v | head -n1)"
echo "Git branch: $(git branch --show-current 2>/dev/null || echo 'n/a')"
echo "Docker: $(docker compose ps --status running --quiet 2>/dev/null | wc -l) containers running"
exit 0
3. Hooks konfigurieren: settings.json und Matcher
Hooks werden in der Datei settings.json konfiguriert, entweder projektweit unter .claude/settings.json oder persoenlich unter ~/.claude/settings.json. Jeder Eintrag besteht aus einem Event-Namen, einem optionalen matcher-Muster, das nur bestimmte Werkzeuge oder Dateipfade erfasst, und einer Liste auszufuehrender Befehle. Der matcher nutzt dabei einfache Muster wie Bash, Edit|Write oder Wildcard-Ausdruecke, um den Hook gezielt auf relevante Faelle zu begrenzen statt bei jedem einzelnen Werkzeugaufruf zu feuern.
Eine haeufige Praxis ist die Trennung nach Umgebung: projektspezifische Hooks, etwa ein Linter, der zum konkreten Projekt passt, gehoeren in .claude/settings.json und werden mit dem Repository versioniert. Persoenliche Hooks, etwa eine Desktop-Benachrichtigung bei Sessionende, gehoeren in die persoenliche Konfiguration. Das folgende Beispiel zeigt eine minimale settings.json mit zwei Hook-Eintraegen fuer unterschiedliche Events.
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{ "type": "command", "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/check-dangerous-commands.sh" }
]
}
],
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{ "type": "command", "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/format-changed-file.sh" }
]
}
]
}
}
4. Ein PreToolUse-Hook fuer Sicherheitsprueffungen
Der praktisch wichtigste Anwendungsfall fuer PreToolUse-Hooks ist das Blockieren gefaehrlicher Befehle, bevor sie ausgefuehrt werden. Der Hook erhaelt ueber stdin ein JSON-Objekt mit dem Namen des Werkzeugs und dessen Eingabeparametern, im Fall von Bash also den kompletten Befehlsstring. Ein Skript kann diesen String gegen eine Liste kritischer Muster wie rm -rf /, DROP TABLE oder ein Schreiben in .env-Dateien pruefen und bei einem Treffer die Ausfuehrung mit Exit-Code 2 verhindern.
Wichtig dabei: Der Exit-Code 2 signalisiert Claude Code, dass der Werkzeugaufruf nicht ausgefuehrt werden soll, und die stderr-Ausgabe des Hooks wird dem Modell als Fehlermeldung zurueckgegeben, sodass es einen alternativen Weg vorschlagen kann. Das folgende Beispiel zeigt ein einfaches Bash-Skript, das genau dieses Muster fuer einen Sicherheits-Hook umsetzt.
#!/usr/bin/env bash
# .claude/hooks/check-dangerous-commands.sh
# Reads PreToolUse hook input (JSON) from stdin, blocks dangerous bash commands
set -euo pipefail
input=$(cat)
command=$(echo "$input" | jq -r '.tool_input.command // empty')
dangerous_patterns=("rm -rf /" "DROP TABLE" "> /dev/sda" ":(){ :|:& };:")
for pattern in "${dangerous_patterns[@]}"; do
if [[ "$command" == *"$pattern"* ]]; then
echo "Blocked: command matches dangerous pattern '$pattern'" >&2
exit 2 # exit code 2 blocks the tool call in Claude Code
fi
done
exit 0 # allow the tool call to proceed
5. Ein PostToolUse-Hook fuer automatisches Formatieren
PostToolUse-Hooks eignen sich fuer Nacharbeiten, die nach jeder Dateiaenderung zuverlaessig laufen sollen, ohne dass Claude sich daran "erinnern" muss. Ein typisches Beispiel ist automatisches Formatieren: Nach jedem Edit- oder Write-Aufruf prueft der Hook, welche Datei geaendert wurde, und ruft je nach Dateiendung den passenden Formatter auf, etwa php-cs-fixer fuer PHP-Dateien oder prettier fuer JavaScript und TypeScript.
Der entscheidende Vorteil gegenueber einer Prompt-Instruktion wie "formatiere nach jeder Aenderung" ist Konsistenz: Ein Hook laeuft garantiert bei jeder passenden Dateiaenderung, unabhaengig davon, wie lang die Konversation bereits ist oder wie stark der Kontext bereits mit anderen Themen gefuellt ist. Gerade in langen Sessions mit vielen Werkzeugaufrufen ist diese Zuverlaessigkeit ein messbarer Unterschied zu rein Prompt-basierter Steuerung.
#!/usr/bin/env bash
# .claude/hooks/format-changed-file.sh
# Reads PostToolUse hook input (JSON) from stdin, formats the touched file
set -euo pipefail
input=$(cat)
file_path=$(echo "$input" | jq -r '.tool_input.file_path // empty')
[[ -z "$file_path" || ! -f "$file_path" ]] && exit 0
case "$file_path" in
*.php)
vendor/bin/php-cs-fixer fix "$file_path" --quiet
;;
*.js|*.ts|*.tsx)
npx prettier --write "$file_path" --loglevel silent
;;
esac
exit 0
6. Hook-Input und Exit-Codes verstehen
Jeder Hook kommuniziert mit Claude Code ueber genau zwei Kanaele: den Exit-Code des Prozesses und optional strukturierte JSON-Ausgabe auf stdout. Exit-Code 0 bedeutet Erfolg, die Ausfuehrung geht normal weiter. Exit-Code 2 bei PreToolUse blockiert den Werkzeugaufruf, die stderr-Meldung wird dem Modell als Kontext mitgegeben. Andere Exit-Codes werden als nicht-blockierender Fehler behandelt, der Nutzer sieht eine Warnung, aber die Ausfuehrung wird nicht gestoppt.
Fuer feingranularere Kontrolle kann ein Hook statt eines einfachen Exit-Codes ein JSON-Objekt auf stdout ausgeben, das Felder wie decision und reason enthaelt. Das erlaubt differenzierte Antworten, etwa "erlauben, aber mit Warnung an das Modell" statt nur "erlauben oder blockieren". Diese Struktur macht das Hook-System flexibel genug fuer einfache Ja-Nein-Entscheidungen ebenso wie fuer komplexere Policy-Logik mit mehreren Abstufungen.
# Manually testing a hook script with a synthetic JSON payload on stdin
echo '{"tool_name":"Bash","tool_input":{"command":"rm -rf /"}}' \
| .claude/hooks/check-dangerous-commands.sh
echo "Exit code: $?" # expect 2, command blocked
echo '{"tool_name":"Bash","tool_input":{"command":"npm test"}}' \
| .claude/hooks/check-dangerous-commands.sh
echo "Exit code: $?" # expect 0, command allowed
7. Hooks versus CLAUDE.md: deterministisch statt Vorschlag
Der zentrale konzeptionelle Unterschied zwischen Hooks und einer Regel in CLAUDE.md ist die Ebene, auf der die Regel durchgesetzt wird. CLAUDE.md wird Teil des Modell-Kontexts und beeinflusst, was das Modell wahrscheinlich als naechstes tut, es bleibt aber eine Instruktion, der das Modell folgen kann, aber nicht muss. Ein Hook laeuft ausserhalb des Modells, auf Betriebssystemebene, und wird unabhaengig vom aktuellen Zustand der Konversation garantiert ausgefuehrt.
Daraus folgt eine klare Faustregel: Alles, was garantiert passieren muss, etwa das Blockieren destruktiver Befehle oder das erzwungene Formatieren vor jedem Commit, gehoert in einen Hook. Alles, was eine Empfehlung oder ein Kontextwissen ist, etwa "dieses Projekt nutzt Repository statt Model direkt", gehoert in CLAUDE.md. Viele produktive Setups kombinieren beides: CLAUDE.md fuer Wissen und Stilrichtlinien, Hooks fuer harte Sicherheits- und Qualitaetsgrenzen, die niemals uebersprungen werden duerfen.
8. Sicherheitsaspekte und typische Fallstricke
Weil Hooks mit den vollen Rechten des aktuellen Nutzers ausgefuehrt werden, tragen sie reales Risiko, wenn sie unbedacht geschrieben werden. Ein Hook-Skript, das selbst nicht validierte Eingaben aus dem Hook-Input ungeprueft in einen weiteren Shell-Befehl einsetzt, kann eine Command-Injection-Luecke oeffnen, genau das Problem, das der Hook eigentlich verhindern sollte. Eingaben aus dem JSON-Input muessen deshalb immer als nicht vertrauenswuerdig behandelt und korrekt gequotet werden.
Ein zweiter haeufiger Fallstrick ist Performance: Ein Hook, der bei jedem PostToolUse-Ereignis einen langsamen, vollstaendigen Test-Lauf startet, verlangsamt jede einzelne Dateiaenderung spuerbar. Besser ist ein gezielter, schneller Formatter statt eines kompletten Test-Durchlaufs, waehrend vollstaendige Tests einem separaten Stop-Hook am Ende einer Antwort vorbehalten bleiben. Ein dritter Fallstrick: Hooks ohne Timeout koennen eine Session blockieren, ein explizites Timeout im Skript selbst verhindert, dass ein haengender Prozess die gesamte Interaktion einfriert.
9. Hook-Events im direkten Vergleich
Die Wahl des richtigen Hook-Events entscheidet, ob eine Automatisierung praeventiv, reaktiv oder informativ wirkt. Die folgende Uebersicht ordnet die wichtigsten Events nach typischem Einsatzzweck ein.
| Event | Zeitpunkt | Kann blockieren | Typischer Einsatz |
|---|---|---|---|
| PreToolUse | Vor dem Werkzeugaufruf | Ja | Sicherheitsprueffungen, verbotene Befehle |
| PostToolUse | Nach dem Werkzeugaufruf | Nein | Formatieren, Linting, Logging |
| UserPromptSubmit | Bei Eingabe des Nutzers | Ja | Kontext injizieren, Eingabe validieren |
| Stop | Nach Antwortende | Teilweise | Vollstaendige Test-Laeufe, Zusammenfassung |
| SessionStart | Bei Session-Beginn | Nein | Umgebungsinfo einmalig laden |
Wer diese Tabelle als Ausgangspunkt nutzt, vermeidet den haeufigsten Konfigurationsfehler: teure, blockierende Logik in einem Event zu platzieren, das eigentlich fuer schnelle, nicht-blockierende Nacharbeit gedacht ist, oder umgekehrt eine Sicherheitspruefung in ein Event zu legen, das gar keine Blockade mehr ermoeglicht.
Mironsoft
Claude Code Setup, Sicherheitsmodelle und Magento/Hyva-Entwicklung mit KI
Verbindliche Regeln statt hoffnungsvoller Prompts?
Wir richten fuer euer Team Hooks fuer Formatierung, Sicherheitsprueffungen und Test-Automatisierung ein, sodass kritische Regeln deterministisch durchgesetzt werden statt nur wahrscheinlich befolgt zu werden.
Sicherheits-Hooks
Gefaehrliche Befehle zuverlaessig blockieren, bevor sie ausgefuehrt werden
Format-Automatisierung
Konsistente Formatierung nach jeder Dateiaenderung ohne manuelles Eingreifen
CI-Integration
Hook-Logik in Pipelines spiegeln fuer konsistente Qualitaetssicherung
10. Zusammenfassung
Das Hook-System von Claude Code loest ein Problem, das reine Prompt-Instruktionen nicht loesen koennen: garantierte, deterministische Durchsetzung von Regeln statt bloss wahrscheinlicher Befolgung. PreToolUse-Hooks blockieren gefaehrliche Befehle vor der Ausfuehrung, PostToolUse-Hooks erzwingen konsistentes Nacharbeiten wie Formatieren, weitere Events wie UserPromptSubmit und SessionStart decken zusaetzliche Steuerungspunkte ab. Konfiguriert werden alle Hooks zentral in settings.json mit Event, Matcher und Befehl.
Der Exit-Code des Hook-Skripts entscheidet ueber Erfolg, Blockade oder nicht-blockierenden Fehler, strukturierte JSON-Ausgabe ermoeglicht feinere Entscheidungen. Wichtig bleibt, Hook-Input niemals ungeprueft in weitere Shell-Befehle einzusetzen und teure Logik nicht in performance-kritische Events zu legen. Wer Hooks fuer harte Sicherheits- und Qualitaetsgrenzen nutzt und CLAUDE.md fuer Wissen und Stil, kombiniert beide Staerken sinnvoll.
Das Hook-System von Claude Code — Das Wichtigste auf einen Blick
Kernidee
Shell-Befehle, die bei definierten Events deterministisch laufen, unabhaengig vom Modellverhalten.
Wichtigste Events
PreToolUse kann blockieren, PostToolUse fuer Nacharbeit, UserPromptSubmit fuer Kontext.
Konfiguration
Zentral in settings.json mit Event, Matcher-Muster und auszufuehrendem Befehl.
Exit-Codes
0 erlaubt, 2 blockiert bei PreToolUse, andere Codes gelten als nicht-blockierender Fehler.