Claude Code Hooks – Praxisguide mit konkreten Rezepten
Die offizielle Dokumentation zu Claude Code Hooks erklärt das System ausführlich. Trotzdem stehe ich immer wieder vor der Frage: Welche Hooks brauche ich konkret für meinen Alltag? Dieses Problem löse ich hier. Statt einer Referenz-Doku bekommen Sie fertige, sofort einsetzbare Rezepte, die Sie 1:1 in Ihre settings.json kopieren können. Jedes Rezept löst ein konkretes Problem, das mir oder anderen Entwicklern im Alltag begegnet ist.
Falls Sie mit Claude Code noch nicht vertraut sind, schauen Sie sich zuerst das Claude Code Cheat Sheet an. Für die CLAUDE.md gibt es einen eigenen Deep Dive.
Hooks auf 30 Sekunden erklärt
Hooks sind Shell-Befehle, die an bestimmten Punkten im Claude-Code-Lebenszyklus automatisch ausgeführt werden. Im Gegensatz zu Anweisungen in der CLAUDE.md, die Claude befolgen soll, sind Hooks deterministisch – sie passieren garantiert, jedes Mal. Sie konfigurieren sie in Ihrer settings.json und brauchen dafür kein Plugin und keine Extension.
# Hooks werden in JSON konfiguriert.
# Die Grundstruktur sieht immer so aus:
{
"hooks": {
"EventName": [ # Wann soll der Hook laufen?
{
"matcher": "Filter", # Optional: Nur bei bestimmten Tools/Events
"hooks": [
{
"type": "command", # Was soll passieren?
"command": "dein-befehl"
}
]
}
]
}
}
# Wo die Datei liegt, bestimmt den Geltungsbereich:
# ~/.claude/settings.json → gilt für alle Projekte
# .claude/settings.json → gilt für dieses Projekt (Git-tracked)
# .claude/settings.local.json → gilt für dieses Projekt (nicht Git-tracked)
Rezept 1: Auto-Format nach jeder Bearbeitung
Problem: Claude schreibt Code, der nicht Ihrem Formatierungs-Standard entspricht. Sie müssen ständig manuell Prettier oder Biome laufen lassen.
Lösung: Ein PostToolUse-Hook, der nach jeder Dateibearbeitung automatisch den Formatter auf die bearbeitete Datei laufen lässt.
# Für Prettier (in .claude/settings.json)
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "jq -r '.tool_input.file_path' | xargs npx prettier --write 2>/dev/null"
}
]
}
]
}
}
# So funktioniert es:
# 1. Claude bearbeitet eine Datei (Edit oder Write Tool)
# 2. Der Hook bekommt die Event-Daten als JSON auf stdin
# 3. jq extrahiert den Dateipfad aus den Tool-Eingaben
# 4. Der Formatter wird auf genau diese Datei angewendet
# 5. 2>/dev/null unterdrückt Fehlermeldungen bei Nicht-JS-Dateien
Rezept 2: ESLint-Feedback nach Dateiänderungen
Problem: Claude schreibt Code, der zwar funktioniert, aber Lint-Regeln verletzt. Sie merken es erst beim nächsten Commit.
Lösung: Claude bekommt nach jeder Bearbeitung sofort ESLint-Feedback und kann Fehler direkt beheben.
# ESLint nach jeder Dateibearbeitung ausführen
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "FILE=$(jq -r '.tool_input.file_path'); if [[ \"$FILE\" =~ \\.(js|ts|tsx|jsx)$ ]]; then npx eslint \"$FILE\" --format compact >&2; fi"
}
]
}
]
}
}
# Was passiert:
# 1. Der Dateipfad wird aus dem JSON extrahiert
# 2. Nur JS/TS-Dateien werden geprüft (if-Bedingung)
# 3. ESLint-Output geht auf stderr, damit Claude die Fehler sieht
# 4. Bei Nicht-JS-Dateien passiert nichts
Rezept 3: Sensible Dateien schützen
Problem: Claude ändert versehentlich .env-Dateien, Lock-Files oder Dateien im .git/-Verzeichnis.
Lösung: Ein PreToolUse-Hook prüft vor jeder Bearbeitung, ob die Zieldatei geschützt ist. Bei einem Treffer wird die Aktion blockiert und Claude bekommt Feedback.
# Speichern Sie als .claude/hooks/protect-files.sh
#!/bin/bash
INPUT=$(cat)
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')
# Liste der geschützten Muster – nach Bedarf anpassen
PROTECTED_PATTERNS=(
".env"
"package-lock.json"
"pnpm-lock.yaml"
"yarn.lock"
".git/"
"dist/"
"node_modules/"
)
for pattern in "${PROTECTED_PATTERNS[@]}"; do
if [[ "$FILE_PATH" == *"$pattern"* ]]; then
echo "Blockiert: $FILE_PATH ist geschützt ('$pattern')" >&2
exit 2 # Exit 2 = Aktion blockieren
fi
done
exit 0 # Exit 0 = alles okay, weitermachen
# Danach ausführbar machen: chmod +x .claude/hooks/protect-files.sh
# Hook registrieren (in .claude/settings.json)
{
"hooks": {
"PreToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "$CLAUDE_PROJECT_DIR/.claude/hooks/protect-files.sh"
}
]
}
]
}
}
# Exit-Codes erklärt:
# 0 = Aktion erlauben
# 2 = Aktion blockieren (Claude bekommt stderr als Feedback)
# Alles andere = Aktion erlauben, stderr wird nur geloggt
Rezept 4: Desktop-Benachrichtigung bei Wartezeit
Problem: Sie stoßen eine Claude-Aufgabe an, wechseln in ein anderes Fenster und merken nicht, dass Claude schon längst fertig ist oder auf Ihre Eingabe wartet.
# macOS (in ~/.claude/settings.json)
{
"hooks": {
"Notification": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "osascript -e 'display notification \"Claude wartet auf Sie\" with title \"Claude Code\" sound name \"Ping\"'"
}
]
}
]
}
}
# Linux (benötigt libnotify):
# "command": "notify-send -u normal 'Claude Code' 'Claude wartet auf Sie'"
# Matcher-Optionen:
# "" → bei allen Benachrichtigungen
# "permission_prompt" → nur bei Berechtigungsanfragen
# "idle_prompt" → nur wenn Claude fertig ist und wartet
Rezept 5: Gefährliche Shell-Befehle abfangen
Problem: Claude könnte versehentlich destruktive Befehle ausführen – rm -rf, DROP TABLE oder git push --force.
# Speichere als .claude/hooks/block-dangerous.sh
#!/bin/bash
INPUT=$(cat)
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty')
DANGEROUS_PATTERNS=(
"rm -rf /"
"rm -rf ~"
"drop table"
"drop database"
"git push.*--force"
"git reset --hard"
"chmod -R 777"
"curl.*| bash"
)
COMMAND_LOWER=$(echo "$COMMAND" | tr '[:upper:]' '[:lower:]')
for pattern in "${DANGEROUS_PATTERNS[@]}"; do
if echo "$COMMAND_LOWER" | grep -qE "$pattern"; then
echo "BLOCKIERT: '$pattern' erkannt in: $COMMAND" >&2
exit 2
fi
done
exit 0
# Hook registrieren
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "$CLAUDE_PROJECT_DIR/.claude/hooks/block-dangerous.sh"
}
]
}
]
}
}
# Matcher "Bash" fängt nur Shell-Befehle ab.
# "Edit|Write" würde nur Dateiänderungen abfangen.
# Ohne Matcher läuft der Hook bei JEDEM Tool.
Rezept 6: Kontext nach Compaction wiederherstellen
Problem: Bei langen Sessions wird das Context Window voll, Claude komprimiert die Konversation und wichtige Details gehen verloren.
# Dynamischen Kontext nach Compaction injizieren
{
"hooks": {
"SessionStart": [
{
"matcher": "compact",
"hooks": [
{
"type": "command",
"command": "echo '--- Kontext nach Compaction ---' && echo 'Branch:' && git branch --show-current 2>/dev/null && echo 'Letzte 5 Commits:' && git log --oneline -5 2>/dev/null && echo 'Geänderte Dateien:' && git diff --name-only HEAD~3 2>/dev/null && echo 'Erinnerung: pnpm nutzen, Tests vor Commits.'"
}
]
}
]
}
}
# Alles was auf stdout geschrieben wird, landet in Claudes Kontext.
# Der Matcher "compact" sorgt dafür, dass der Hook nur
# nach einer Komprimierung läuft, nicht bei jedem Session-Start.
# Für Kontext bei JEDEM Start: nutzen Sie CLAUDE.md stattdessen.
Rezept 7: Alle Bash-Befehle loggen
Problem: Sie wollen nachverfolgen, welche Shell-Befehle Claude während einer Session ausgeführt hat – zur Dokumentation, zum Debugging oder für Compliance.
# Jeden Bash-Befehl mit Zeitstempel loggen
{
"hooks": {
"PostToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "jq -r '\"[\" + (now | todate) + \"] \" + .tool_input.command' >> ~/.claude/command-log.txt"
}
]
}
]
}
}
# Ergebnis in ~/.claude/command-log.txt:
# [2026-03-31T10:15:23Z] npm test
# [2026-03-31T10:15:45Z] git diff --name-only
# [2026-03-31T10:16:02Z] npx tsc --noEmit
Rezept 8: Type-Check nach TypeScript-Änderungen
Problem: Claude erzeugt TypeScript-Code, der funktional aussieht, aber Type-Fehler hat. Sie merken es erst beim Build.
# Type-Check nur bei TypeScript-Dateien
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "FILE=$(jq -r '.tool_input.file_path'); if [[ \"$FILE\" =~ \\.(ts|tsx)$ ]]; then npx tsc --noEmit --pretty 2>&1 | head -20 >&2; fi"
}
]
}
]
}
}
# head -20 begrenzt die Ausgabe (bei vielen Fehlern sonst riesig)
# >&2 schickt das Ergebnis auf stderr, damit Claude es sieht
Rezept 9: Prompt-Hook – Ist die Aufgabe fertig?
Problem: Claude sagt, es ist fertig, hat aber nicht alle Teilaufgaben erledigt.
Lösung: Ein Stop-Hook vom Typ prompt. Statt eines Shell-Befehls bewertet ein separates Claude-Modell, ob die Aufgabe wirklich abgeschlossen ist.
# Prompt-basierter Stop-Hook
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "prompt",
"prompt": "Prüfe ob alle Aufgaben erledigt sind. Falls nicht: {\\\"ok\\\": false, \\\"reason\\\": \\\"Was noch fehlt...\\\"}"
}
]
}
]
}
}
# Hook-Typen erklärt:
# "command" → führt Shell-Befehl aus (deterministisch)
# "prompt" → fragt ein LLM (Standard: Haiku) für Ja/Nein-Entscheidung
# "agent" → spawnt einen Subagent mit Tool-Zugriff
# "http" → POSTet Event-Daten an eine URL
# WICHTIG: Stop-Hooks können Endlosschleifen verursachen!
# Das Modell muss stop_hook_active prüfen.
Rezept 10: Agent-Hook – Tests vor dem Stoppen
Problem: Claude behauptet, die Implementierung sei fertig, aber die Tests laufen nicht durch.
# Agent-basierter Stop-Hook: Tests müssen grün sein
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "agent",
"prompt": "Führe die Test-Suite aus und verifiziere, dass alle Tests bestehen. $ARGUMENTS",
"timeout": 120
}
]
}
]
}
}
# Unterschied prompt vs. agent:
# prompt: Ein einzelner LLM-Aufruf, bewertet nur die Hook-Daten
# agent: Spawnt Subagent mit echtem Tool-Zugriff
# (Dateien lesen, Befehle ausführen, Code durchsuchen)
#
# Nutzen Sie "prompt" wenn die Hook-Eingabedaten reichen.
# Nutzen Sie "agent" wenn Sie gegen den echten Code-Zustand prüfen müssen.
Troubleshooting
Hooks können frustrierend sein, wenn sie nicht funktionieren. Hier die häufigsten Stolperfallen.
# Problem: Hook wird nicht ausgelöst
# 1. Prüfen Sie mit /hooks ob der Hook sichtbar ist
# 2. Matcher sind case-sensitive: "bash" ≠ "Bash"
# 3. PreToolUse läuft VOR dem Tool, PostToolUse DANACH
# Problem: "jq: command not found"
brew install jq # macOS
apt-get install jq # Debian/Ubuntu
# Problem: JSON-Parsing-Fehler
# Shell-Profil gibt Text aus, der dem JSON vorangestellt wird.
# Lösung: Echo nur in interaktiven Shells:
if [[ $- == *i* ]]; then
echo "Shell ready"
fi
# Problem: Stop-Hook läuft endlos
# stop_hook_active prüfen:
INPUT=$(cat)
if [ "$(echo "$INPUT" | jq -r '.stop_hook_active')" = "true" ]; then
exit 0 # Claude darf stoppen
fi
# Problem: Script wird nicht ausgeführt
chmod +x ./dein-script.sh
# Debugging: Ctrl+O für Verbose-Modus oder:
claude --debug
Fazit
Hooks verwandeln Claude Code von einem reaktiven Tool in einen proaktiven Workflow-Automaten. Die drei Hooks, die ich jedem empfehle: Auto-Format nach Bearbeitungen (weil es nervt, das manuell zu machen), Dateischutz für sensible Dateien (weil Unfälle passieren) und Desktop-Benachrichtigungen (weil Multitasking ohne sie nicht funktioniert).
Fangen Sie mit einem einzelnen Hook an und bauen Sie von dort aus weiter. Sie werden schnell merken, welche zusätzlichen Automatisierungen in Ihrem Projekt Sinn ergeben. Und denken Sie daran: Hooks und CLAUDE.md ergänzen sich. Hooks garantieren, dass etwas passiert. Die CLAUDE.md gibt Claude den Kontext, um die richtigen Entscheidungen zu treffen.
Weiterführend
- KI-Glossar – Fachbegriffe rund um KI verständlich erklärt
- Claude Code Cheat Sheet
- CLAUDE.md – Der ultimative Guide
