Claude Code Hooks: Regeln des Agenten in Garantien verwandeln
Hooks in Claude Code sind Shell-Befehle, die Claude Code selbst bei bestimmten Ereignissen startet: vor und nach einem Werkzeugaufruf, beim Absenden eines Prompts, beim Sitzungsstart und vor dem Beenden des Agenten. Sie werden in der settings.json eingerichtet, und ein Exit-Code 2 blockiert die Aktion und gibt dem Agenten eine Fehlermeldung zurück. So macht der Hook aus einer Regel aus der CLAUDE.md eine Garantie, die das Modell nicht vergessen kann.
Was Hooks in Claude Code sind
Ein Hook ist ein Befehl, den nicht das Modell startet, sondern das Programm Claude Code selbst in einem festgelegten Moment der Arbeit des Agenten. Der Agent will rm -rf ausführen — der Hook läuft und entscheidet, ob der Befehl durchgelassen wird. Der Agent hat eine Datei geändert — der Hook startet einen Formatierer. Der Agent will die Arbeit beenden — der Hook führt die Tests aus und lässt ihn nicht anhalten, solange sie rot sind.
Der entscheidende Unterschied zu Anweisungen in der CLAUDE.md: Eine Anweisung ist Text, den das Modell mit einer gewissen Wahrscheinlichkeit berücksichtigt. Ein Hook ist deterministischer Code. Er läuft immer, unabhängig davon, wie lang der Kontext ist, was der Agent für richtig hält und ob er die Regel nach hundert Minuten Sitzung vergessen hat. Deshalb sind Hooks der wichtigste Weg, einen Wunsch in eine Garantie zu verwandeln.
Warum Regeln in der CLAUDE.md nicht reichen
Eine Regel wie »Führe keine Befehle mit force-push aus« steht in der CLAUDE.md, und trotzdem kann der Agent sie in der dreißigsten Minute übersehen, besonders wenn der Kontext voll ist. Das ist kein Fehler des Modells, sondern eine Eigenschaft langer Sitzungen: Die Regel steht zwar im Kontext, aber sie konkurriert mit allem anderen darin. Eine Anweisung hilft beim Verstehen, sie schützt aber nicht vor Versehen.
Ein Hook hat dieses Problem nicht. Er prüft jeden Befehl oder jede Änderung, bevor sie ausgeführt wird, unabhängig von der Aufmerksamkeit des Modells. Die Regel muss dann nicht mehr dreimal in der CLAUDE.md wiederholt werden — eine Zeile genügt, die auf den Hook verweist.
Ereignisse: Wann Hooks laufen
Jeder Hook ist an ein Ereignis im Lebenszyklus der Sitzung gebunden. Die wichtigsten Ereignisse:
| Ereignis | Wann es auslöst | Typische Verwendung |
|---|---|---|
PreToolUse | vor dem Aufruf eines Werkzeugs (Bash, Edit, Write u. a.) | Sperren gefährlicher Befehle, Schutz von Dateien |
PostToolUse | nach einem erfolgreichen Werkzeugaufruf | Formatierer, Linter, schnelle Tests nach einer Änderung |
UserPromptSubmit | wenn der Benutzer einen Prompt abgeschickt hat, vor der Verarbeitung | Kontext ergänzen, Prompt mit einem Geheimnis abweisen |
Stop | wenn der Agent seine Antwort beenden will | Verhindern, dass er anhält, solange Prüfungen nicht bestanden sind |
SubagentStop | wenn ein Subagent seine Arbeit beendet | Prüfung des Ergebnisses des Subagenten |
SessionStart | beim Start oder Fortsetzen der Sitzung | Zustand laden: git status, Plan, Fortschrittsjournal |
SessionEnd | beim Beenden der Sitzung | Aufräumen, Schreiben von Protokollen |
Notification | wenn Claude Code eine Benachrichtigung sendet (etwa wenn auf eine Freigabe gewartet wird) | Ton oder Nachricht aufs Telefon |
PreCompact | vor der Komprimierung des Kontexts | Wichtiges vor der Komprimierung sichern |
Für die Zuverlässigkeit am nützlichsten sind PreToolUse, PostToolUse und Stop: Sie decken die drei Schwachstellen des Agenten ab — gefährliche Aktionen, ungeprüfte Änderungen und ein vorzeitiges »fertig«.
Wo Hooks eingerichtet werden: settings.json
Hooks werden in den Einstellungsdateien von Claude Code beschrieben:
~/.claude/settings.json— Ihre persönlichen Einstellungen für alle Projekte;.claude/settings.jsonim Repository — gemeinsam für das Team, wird in git committet;.claude/settings.local.json— lokal für das Projekt, landet nicht in git.
Die Struktur ist: Ereignis → Liste von Gruppen mit matcher → Liste von Befehlen.
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/lint.sh",
"timeout": 60
}
]
}
]
}
}
matcher ist der Name eines Werkzeugs oder ein regulärer Ausdruck darauf: Bash, Edit|Write, .*. Er wird für Ereignisse benötigt, die mit Werkzeugen zusammenhängen; bei Stop oder UserPromptSubmit kann er entfallen. Die Variable CLAUDE_PROJECT_DIR zeigt auf das Projektverzeichnis, deshalb legen Sie Skripte am besten in .claude/hooks/ ab und rufen sie darüber auf. Die aktuellen Hooks zeigen Sie mit dem Befehl /hooks innerhalb von Claude Code an.
Wie ein Hook Daten bekommt und antwortet
Eingabe: JSON auf stdin
Claude Code übergibt dem Hook ein JSON mit den Angaben zum Ereignis: Sitzungs-ID, Arbeitsverzeichnis, Name des Ereignisses und bei Werkzeugen tool_name und tool_input. Bei Bash steht der Befehl in tool_input.command, bei Edit und Write der Dateipfad in tool_input.file_path. Bequem lesen lässt sich das mit jq.
{
"session_id": "abc123",
"hook_event_name": "PreToolUse",
"tool_name": "Bash",
"tool_input": { "command": "rm -rf build/" },
"cwd": "/home/dev/project"
}
Ausgabe: Rückgabecode
| Code | Was passiert |
|---|---|
0 | Erfolg, die Arbeit geht weiter; bei UserPromptSubmit und SessionStart wird die stdout-Ausgabe in den Kontext des Agenten übernommen |
2 | blockierender Fehler: stderr geht an den Agenten, und die Aktion wird angehalten (bei PreToolUse wird der Werkzeugaufruf abgebrochen, bei Stop arbeitet der Agent weiter) |
| anderer | nicht blockierender Fehler: stderr wird dem Benutzer angezeigt, die Arbeit läuft weiter |
Der Exit-Code 2 ist der Schlüssel. Der Agent erhält den Text aus stderr als Rückmeldung und kann sich korrigieren: den Befehl umschreiben, den Test reparieren, die Datei unangetastet lassen. Schreiben Sie deshalb in stderr keinen bloßen Fehler, sondern den verständlichen Grund und was stattdessen zu tun ist.
Zusätzlich kann ein Hook in stdout ein JSON mit einer Entscheidung ausgeben, zum Beispiel "decision": "block" und "reason". Das dient der Feinsteuerung; für die meisten Aufgaben reichen Code 2 und eine Meldung in stderr.
Beispiel 1: Linter und Tests nach jeder Änderung
Ein Hook bei PostToolUse mit dem Matcher Edit|Write läuft nach einer Änderung an einer Datei. Das Werkzeug ist schon ausgeführt, die Änderung lässt sich nicht mehr zurücknehmen, aber Code 2 gibt dem Agenten die Ausgabe des Linters zurück, und er behebt den Fehler sofort und nicht eine halbe Stunde später.
#!/usr/bin/env bash
# .claude/hooks/lint.sh
file=$(jq -r '.tool_input.file_path // empty')
case "$file" in
*.py)
if ! out=$(ruff check "$file" 2>&1); then
echo "Der Linter hat Fehler in $file gefunden:" >&2
echo "$out" >&2
exit 2
fi ;;
*.php)
if ! out=$(php -l "$file" 2>&1); then
echo "$out" >&2
exit 2
fi ;;
esac
exit 0
Hängen Sie hier keinen vollständigen Testlauf von zehn Minuten an: Der Hook läuft bei jeder Änderung. Nach einer Änderung gehören schnelle Prüfungen hinein (Syntax, Linter, Tests eines Moduls), die schweren Prüfungen in den Hook Stop.
Beispiel 2: Sperren gefährlicher Befehle
Ein Hook bei PreToolUse mit dem Matcher Bash sieht den Befehl, bevor er ausgeführt wird. Code 2 bricht den Aufruf ab, und der Agent erhält eine Erklärung.
#!/usr/bin/env bash
# .claude/hooks/guard.sh
cmd=$(jq -r '.tool_input.command // empty')
if echo "$cmd" | grep -Eq 'rm -rf /|git push (-f|--force)|DROP (TABLE|DATABASE)'; then
echo "Der Befehl wurde durch die Richtlinie des Projekts blockiert: $cmd. Gefährliche Operationen führt ein Mensch aus." >&2
exit 2
fi
exit 0
Ähnlich schützen Sie Dateien: Ein Hook bei Edit|Write prüft file_path und blockiert das Schreiben in .env, in Produktionskonfigurationen, in generierten Code oder in bereits angewendete Migrationsdateien. Beachten Sie, dass sich eine Suche per regulärem Ausdruck umgehen lässt — der Agent kann einen Befehl anders schreiben. Der Hook ist eine starke Absicherung, aber kein Ersatz für Zugriffsrechte und eine Sandbox.
Beispiel 3: Der Agent kann nicht »fertig« sagen, solange die Tests rot sind
Der Hook Stop läuft, wenn der Agent beenden will. Sind die Tests nicht bestanden, hindert ihn Code 2 am Anhalten, und die Ausgabe der Tests geht als nächste Aufgabe an den Agenten. Das ist die mechanische Form der Definition of Done.
#!/usr/bin/env bash
# .claude/hooks/done.sh
input=$(cat)
# Schutz vor einer Endlosschleife: der Hook hat die Arbeit bereits einmal verlängert
if [ "$(echo "$input" | jq -r '.stop_hook_active')" = "true" ]; then
exit 0
fi
if ! out=$(npm test --silent 2>&1); then
echo "Tests sind nicht bestanden, die Arbeit ist nicht abgeschlossen:" >&2
echo "$out" | tail -n 40 >&2
exit 2
fi
exit 0
Das Feld stop_hook_active ist true, wenn der Agent die Arbeit bereits auf Grund eines Stop-Hooks fortsetzt. Ohne diese Prüfung kann der Hook den Agenten endlos im Kreis schicken — genau die Situation, in der der Agent »die ganze Nacht in die falsche Richtung arbeitet«. Bei autonomen Schleifen ergänzen Sie zusätzlich eine Grenze für Versuche und eine Bedingung, bei der ein Mensch hinzugerufen wird.
Beispiel 4: Zustand zu Beginn der Sitzung laden
Bei SessionStart gelangt die stdout-Ausgabe des Hooks in den Kontext. Praktisch ist es, den aktuellen Branch, nicht committete Änderungen und die nächste Aufgabe aus dem Plan auszugeben, damit der Agent nicht bei null anfängt.
#!/usr/bin/env bash
echo "Branch: $(git branch --show-current)"
git status --short | head -n 20
[ -f PROGRESS.md ] && tail -n 15 PROGRESS.md
exit 0
Wie Hooks Regeln in Garantien verwandeln
Die Regeln des Harness lassen sich in drei Stufen einteilen. Die erste ist Text in der CLAUDE.md: Er erklärt, wie zu arbeiten ist, wird aber nur mit Wahrscheinlichkeit befolgt. Die zweite sind ausführbare Prüfungen: Tests, End-to-End-Tests, Architekturregeln als Skript, das bei einem Verstoß fehlschlägt. Die dritte sind Hooks, die diese Prüfungen im richtigen Moment automatisch starten, ohne Zutun des Modells.
Praktische Faustregel: Wenn der Agent dieselbe Regel zweimal verletzt hat, fügen Sie der CLAUDE.md keine weitere Warnung in Großbuchstaben hinzu. Fragen Sie stattdessen, ob sich der Verstoß per Skript prüfen lässt. Wenn ja, schreiben Sie das Skript und hängen Sie es an einen Hook. Dann wird der Textabschnitt kürzer und das Verhalten stabiler.
| Regel im Text | Garantie über einen Hook |
|---|---|
| »Formatiere den Code nach dem Standard« | PostToolUse startet den Formatierer für die geänderte Datei |
| »Pushe nicht mit force« | PreToolUse bei Bash blockiert den Befehl, exit 2 |
| »Sag nicht fertig, solange die Tests rot sind« | Stop startet die Tests und lässt die Arbeit nicht enden |
| »Bearbeite nicht die .env« | PreToolUse bei Edit|Write prüft den Pfad |
| »Denk daran, wo du aufgehört hast« | SessionStart gibt Plan und git status aus |
Typische Fehler
- Code 1 statt 2. Das Skript bricht mit Code 1 ab, der Agent erfährt nichts, und die Aktion läuft durch. Blockieren kann nur Code 2.
- Meldung auf stdout. Bei Code 2 geht stderr an den Agenten. Schreiben Sie über
>&2. - Langsamer Hook bei häufigem Ereignis. Ein vollständiger Testlauf in PostToolUse macht jede Änderung zur Minutenaufgabe. Schwere Prüfungen gehören in Stop.
- Endloses Stop. Ohne Prüfung von
stop_hook_activeoder ohne Grenze dreht sich der Agent ohne Ende. - Relative Pfade. Der Hook läuft im Arbeitsverzeichnis der Sitzung; verwenden Sie
$CLAUDE_PROJECT_DIR. - Ungeprüfter fremder Hook. Der Hook läuft mit Ihren Rechten und ohne Bestätigung. Lesen Sie Skripte aus fremden Repositories, bevor Sie ihnen vertrauen.
Fazit
Hooks in Claude Code übertragen die wichtigsten Regeln aus dem Text in den Code: PreToolUse hält gefährliche Aktionen an, PostToolUse prüft jede Änderung, Stop verhindert eine vorzeitige Meldung der Fertigstellung, SessionStart gibt dem Agenten das Gedächtnis über das Projekt zurück. Beginnen Sie mit zwei bis drei Hooks für Regeln, die der Agent schon verletzt hat, und formulieren Sie die Meldungen in stderr verständlich. Dann korrigiert der Agent Fehler selbst, und Sie prüfen das Ergebnis statt des Prozesses. Die Validierung, die Prüfung der Fertigstellung und autonome Schleifen behandeln wir im Kurs praktisch.
Häufige Fragen
Muss ich Claude Code nach einer Änderung an den Hooks neu starten?
Änderungen in der settings.json werden am zuverlässigsten in einer neuen Sitzung übernommen; die aktuelle Hook-Konfiguration zeigen Sie mit dem Befehl /hooks an.
Kann ich Hooks nicht in Bash schreiben?
Doch. Ein Hook ist jeder Befehl: ein Skript in Python, Node.js oder ein Binärprogramm. Wichtig ist nur, dass er das JSON von stdin liest und den richtigen Exit-Code zurückgibt.
Sieht der Agent die Ausgabe eines erfolgreichen Hooks?
In der Regel nicht: Bei Code 0 gelangt die Ausgabe nicht in den Kontext. Ausnahmen sind UserPromptSubmit und SessionStart, bei denen stdout in den Kontext des Agenten übernommen wird.
Gelten Hooks auch für Subagenten?
Ja, Werkzeugaufrufe von Subagenten laufen über dieselben Hooks PreToolUse und PostToolUse, für ihr Ende gibt es das eigene Ereignis SubagentStop.
Ist es sicher, Hooks ins Repository zu committen?
Ja, die .claude/settings.json und die Skripte in .claude/hooks/ sind für ein Team übliche Praxis; persönliche Hooks halten Sie in settings.local.json oder in ~/.claude/settings.json.
Dieses Thema wird im Kurs „Harness Engineering: инженерия надёжности AI-агентов“ ausführlich mit Praxis behandelt.
- Агент написал «готово», а фича не работает: verification gap и Definition of Done
- Все тесты зелёные, а фича сломана: E2E-тесты и архитектурные правила для AI-агента
- Автономный AI-агент часами без присмотра: Loop Engineering и условия остановки