Claude Code Hooks: So blocken Sie riskante Befehle
Claude Code Hooks sind Befehle, die an festen Punkten einer Session ausgeführt werden: bevor ein Tool läuft, nachdem es gelaufen ist, wenn ein Prompt gesendet wird, wenn die Session endet. Sie sind der Weg, aus „der Agent soll das nicht tun“ etwas zu machen, das tatsächlich geprüft wird. Dieser Artikel zeigt Claude-Code-Hooks-Beispiele, die blockieren, was blockiert werden muss, wie man sie testet und welche Fehler hinter den meisten Suchanfragen zu „hook error“ stecken.
Alles im Folgenden orientiert sich an der offiziellen Hooks-Referenz und der Permissions-Dokumentation. Wir setzen Claude Code mit Hooks täglich in unserem eigenen Studio ein; einer davon ist ein Stop-Hook, der warnt, wenn die Token-Nutzung einer Session ein Limit überschreitet.
Wo Claude Code Hooks konfiguriert werden
Hooks leben in Settings-Dateien:
| Datei | Gilt für | Geteilt |
|---|---|---|
~/.claude/settings.json |
alle Ihre Projekte | nein |
.claude/settings.json |
ein Projekt | ja, kann eingecheckt werden |
.claude/settings.local.json |
ein Projekt | nein |
Einträge aus diesen Dateien werden zusammengeführt, nicht ersetzt. Auch Plugins, Skills und Subagents können eigene Hooks mitbringen.
Ein Hook-Eintrag hat drei Ebenen: das Event, eine Matcher-Gruppe und die Handler:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{ "type": "command", "command": "python3 ${CLAUDE_PROJECT_DIR}/.claude/hooks/block_rm.py" }
]
}
]
}
}
CLAUDE_PROJECT_DIR ist der Projekt-Root, in dem die Session gestartet wurde. Rufen Sie Python unter Windows so auf, wie es Ihr System erwartet.
Ein PreToolUse-Hook, der einen riskanten Befehl blockiert
Ein PreToolUse-Hook erhält den geplanten Tool-Aufruf als JSON auf der Standard-Eingabe: tool_name, tool_input und einige Session-Felder. Für Bash ist tool_input.command der Befehlstext.
#!/usr/bin/env python3
import json, re, sys
data = json.load(sys.stdin)
cmd = data.get("tool_input", {}).get("command", "")
if re.search(r"\brm\s+-\w*(rf|fr)\w*\b", cmd):
print("Blocked: recursive force delete. Ask the user to run it.", file=sys.stderr)
sys.exit(2)
sys.exit(0)
Die entscheidende Regel: Exit-Code 2 blockiert den Aufruf, und stderr wird zur Begründung, die Claude sieht. Exit 0 lässt ihn durch. Jeder andere Code ist ein nicht blockierender Fehler: Die Aktion wird ausgeführt, und Sie erhalten nur einen Hinweis.
Ein Textmuster erkennt die übliche Form eines Befehls, aber nicht jede Form: Dieses hier stoppt rm -rf und rm -fr, aber rm -r -f mit getrennten Flags kommt durch. Deshalb braucht jede Regel Tests, und deshalb gibt es neben Hooks auch Permission-Regeln und die Sandbox.
Wenn Sie strukturierte Ausgaben bevorzugen: Ein Hook kann mit Exit 0 enden und JSON mit hookSpecificOutput ausgeben, wobei hookEventName PreToolUse ist und permissionDecision allow, deny, ask oder defer lautet, zusammen mit einem permissionDecisionReason.
Der PreToolUse-Matcher, ohne Überraschungen
Der Matcher filtert nach dem Tool-Namen:
Bashoder eine Liste wieEdit|Write— exakte Namen.- Alles mit anderen Zeichen ist ein regulärer Ausdruck, nicht verankert:
^Notebook,mcp__memory__.*. - MCP-Tools heißen
mcp__<server>__<tool>. Ein Matcher wiemcp__memoryallein trifft auf nichts zu; Sie brauchenmcp__memory__.*. *, ein leerer String oder kein Matcher bedeutet jedes Tool.
Es gibt außerdem ein if-Feld mit der Syntax der Permission-Regeln, etwa Bash(git *). Die Dokumentation nennt es best-effort: Für ein hartes allow oder deny nutzen Sie das Permission-System.
Testen Sie jeden Hook: ein Aufruf blockiert, ein Aufruf erlaubt
Ein Hook, den Sie nie getestet haben, ist ein Hook, von dem Sie hoffen, dass er funktioniert. Der günstigste Test führt ihm dasselbe JSON zu, das Claude Code senden würde, und prüft den Exit-Code:
echo '{"tool_name":"Bash","tool_input":{"command":"rm -rf build"}}' | python3 .claude/hooks/block_rm.py; echo "exit $?" # expect 2
echo '{"tool_name":"Bash","tool_input":{"command":"ls build"}}' | python3 .claude/hooks/block_rm.py; echo "exit $?" # expect 0
Unsere Regel für jeden Hook: eine Aktion, die blockiert werden muss, und eine, die durchgehen muss — beide in der Testdatei. Der zweite Test findet den anderen häufigen Fehler: ein Muster, so breit, dass es normale Arbeit blockiert.
Warum ein PreToolUse-Hook-Fehler nichts stoppt
Die üblichen Ursachen, alle aus der Referenz:
- Exit-Code 1 statt 2. Es erscheint ein Fehlerhinweis, und das Tool läuft trotzdem.
- Timeout. Ein PreToolUse-Command-Hook, der in einen Timeout läuft, blockiert nicht; der Aufruf geht weiter durch den normalen Permission-Ablauf. Command-Hooks haben standardmäßig 600 Sekunden.
- Ein Matcher, der nie trifft, wie
mcp__memoryohne.*. - Ein fehlerhaftes Skript. Standardmäßig blockiert ein fehlschlagender Hook nicht. Neuere Versionen unterstützen
"onFailure": "block"(v2.1.295 oder später), um zu blockieren, wenn ein Command-Hook fehlschlägt, in einen Timeout läuft oder eine ungültige Ausgabe liefert.
Hooks, Permission-Regeln und Skills: wer was tut
- Permission-Regeln (
allow,ask,denyin settings.json) werden in dieser Reihenfolge geprüft: deny, dann ask, dann allow. Eine Deny-Regel greift sogar dann, wenn ein PreToolUse-Hookallowzurückgibt. - Ein blockierender Hook (Exit 2) stoppt den Aufruf, bevor die Permission-Regeln ausgewertet werden, setzt sich also auch gegen eine Allow-Regel durch.
- Skills sind Anweisungen und Ressourcen, die der Agent bei Bedarf lädt. Sie lenken; sie erzwingen nicht.
Beginnen Sie bei vertraulichen Ordnern mit Deny-Regeln wie Read(./secrets/**). Kennen Sie ihre Grenze: Sie decken die integrierten Datei-Tools und Befehle wie cat, head und tail ab, aber nicht grep -r aus dem Ordner oder ein Skript, das selbst Dateien öffnet. Ein PreToolUse-Hook kann in solchen Fällen den vollständigen Befehlstext prüfen, und für Durchsetzung auf Betriebssystemebene verweist die Dokumentation auf die Sandbox.
Wann Sie es abgeben sollten
Wenn Ihr Agent-Setup schnell gewachsen ist und Sie nicht sicher sind, welche Regeln tatsächlich greifen, kartieren wir die Agents, schreiben die Hook-Regeln mit Tests und zeigen den Testlauf, nicht nur die Konfiguration. Wie unser eigenes System aufgebaut ist, sehen Sie auf der Work-Seite.
Fragen
Warum blockiert mein PreToolUse-Hook nichts?
Meistens beendet er sich mit Exit-Code 1. Nur Exit-Code 2 blockiert den Tool-Aufruf; jeder andere Code ungleich null ist ein nicht blockierender Fehler, und die Aktion wird ausgeführt. Auch ein Hook, der in einen Timeout läuft, blockiert nicht.
Brauche ich hooks, wenn ich bereits Permission-Deny-Regeln habe?
Für einfache Regeln haben Deny-Regeln Vorrang: Sie werden sogar dann geprüft, wenn ein Hook allow zurückgibt. Hooks sind für Logik, die ein Muster nicht ausdrücken kann, etwa die Prüfung des gesamten Befehlstexts vor der Ausführung.
Stoppen Read-Deny-Regeln auch cat und grep in Bash?
Sie decken die integrierten Datei-Tools und die Datei-Befehle ab, die Claude Code in Bash erkennt, etwa cat, head und tail. Nicht abgedeckt ist ein Befehl, der Dateien liest, ohne sie zu nennen, wie grep -r aus dem Ordner, oder ein Skript, das selbst Dateien öffnet. Für solche Fälle verweist die Dokumentation auf die Sandbox.
Wo werden Claude Code Hooks konfiguriert?
In Settings-Dateien: ~/.claude/settings.json für alle Ihre Projekte, .claude/settings.json für ein Projekt und Ihr Team oder .claude/settings.local.json nur für Sie selbst. Einträge aus mehreren Dateien werden zusammengeführt.