SSD Nodes Learn 🎉 VPS ab $5.50/Monat
Anleitungen Matt ConnorVon Matt Connor · Aktualisiert 2026-08-15

Claude Code Hooks: Ereignisse, Exit Code 2 und Sicherheit

Erfahren Sie, wo Claude-Code-Hooks liegen, welche Ereignisse sie auslösen und warum Exit Code 2 einen Tool-Aufruf vor der Ausführung abbricht.

Was ein Claude-Code-Hook ist

Claude-Code-Hooks sind Shell-Befehle, die Claude Code an festgelegten Punkten in seinem eigenen Lebenszyklus selbst ausführt. Das ist der gesamte Unterschied zwischen einem Hook und einer Regeldatei. Eine Anweisung in CLAUDE.md ist eine Empfehlung, und das Modell gewichtet sie gegenüber allen anderen Informationen in seinem Kontext. Ein Hook ist Code, und er wird ausgeführt, unabhängig davon, ob das Modell zustimmt. Wenn Ihr Agent den Formatter weiterhin überspringt, obwohl Sie ihn zweimal darauf hingewiesen haben, benötigen Sie keine strengere Anweisung. Sie benötigen einen Hook.

Der Mechanismus ist klein. Sie registrieren einen Befehl in einer Einstellungsdatei unter einem Ereignisnamen. Wenn dieses Ereignis ausgelöst wird, führt Claude Code Ihren Befehl aus und schreibt die Ereignisdaten als JSON (JavaScript Object Notation) in dessen Standardeingabe (stdin). Ihr Befehl liest diese Daten, führt seine Aufgabe aus und beendet sich mit einem Exit-Status. Exit 2 bei einem PreToolUse-Hook bricht den Tool-Aufruf ab, bevor er ausgeführt wird. Alles, was Ihr Skript in die Standardfehlerausgabe (stderr) schreibt, wird dem Modell als Begründung übergeben.

Die Ereignis- und Feldnamen in diesem Abschnitt stammen aus der Hooks-Referenz von Claude Code, die im August 2026 anhand von Release 2.1.232 geprüft wurde. Diese Schnittstelle ändert sich schnell. Prüfen Sie daher die Referenz für Ihre eigene Version, bevor Sie JSON aus einem Blogbeitrag kopieren, auch aus diesem. Geben Sie Ihre Version mit claude --version aus.

Wo die Hook-Konfiguration liegt

Ein Hook ist ein JSON-Block in einer Einstellungsdatei. Sechs Speicherorte können einen solchen Block enthalten. Der Geltungsbereich der Datei bestimmt den Geltungsbereich des Hooks.

  • ~/.claude/settings.json: jedes Projekt auf Ihrem Rechner, aber auf keinem anderen.
  • .claude/settings.json: ein Projekt, wird in das Repository übernommen, sodass jeder, der es klont, den Hook erhält.
  • .claude/settings.local.json: ein Projekt, nur auf Ihrem Rechner.
  • Verwaltete Richtlinieneinstellungen: organisationsweit, von einem Administrator festgelegt.
  • hooks/hooks.json innerhalb eines Plugins: aktiv, solange das Plugin aktiviert ist.
  • Frontmatter eines Skills oder Subagents: aktiv, solange diese Komponente aktiv ist.

Hook-Einträge aus diesen Dateien werden zusammengeführt und überschreiben einander nicht. Eine Projekteinstellungsdatei fügt ihre Hooks zu den Hooks in den Benutzereinstellungen hinzu, statt sie zu ersetzen. Daher können für ein Ereignis mehrere Hooks aus mehreren Dateien hinterlegt sein. Mit "disableAllHooks": true werden sie deaktiviert. Eine Ausnahme gilt für Hooks aus verwalteten Richtlinieneinstellungen: Sie bleiben aktiv, sofern diese Einstellung nicht ebenfalls in den verwalteten Einstellungen gesetzt wird.

Führen Sie innerhalb einer Sitzung /hooks aus, um alle derzeit registrierten Hooks nach Ereignis gruppiert aufzulisten. Für jeden Hook werden die Quelldatei und der Matcher angezeigt. Das Menü ist schreibgeschützt. Einen Hook ändern Sie daher durch Bearbeiten der Einstellungsdatei. Der Dateiüberwacher erkennt die Änderung normalerweise ohne Neustart.

Welche Claude-Code-Hook-Ereignisse gibt es?

Release 2.1.232 führt einunddreißig Ereignisse auf, von SessionStart bis SessionEnd. Sie decken Kompaktierung, Subagents, Worktrees und Konfigurationsdateien ab. Für Serverarbeiten benötigen Sie nur einige davon.

  • PreToolUse: bevor ein Tool-Aufruf ausgeführt wird. Dieses Ereignis kann den Aufruf blockieren.
  • PostToolUse: nachdem ein Tool-Aufruf erfolgreich war. PostToolUseFailure wird stattdessen bei einem Fehler ausgelöst. Ein Hook, der jedes Ergebnis erfassen muss, benötigt daher beide Ereignisse.
  • PermissionRequest: wenn ein Tool-Aufruf eine Berechtigungsentscheidung erfordert. Zu diesem Zeitpunkt würde die Bestätigungsabfrage erscheinen.
  • UserPromptSubmit: wenn Sie einen Prompt senden, bevor Claude ihn verarbeitet. Alles, was dieser Hook nach stdout schreibt, wird zum Kontext des Modells hinzugefügt.
  • SessionStart und SessionEnd: jeweils am Ende einer Sitzung. SessionStart wird unter dem Matcher-Wert compact auch nach einer Kompaktierung ausgelöst.
  • Stop: wenn Claude seine Antwort beendet. Das geschieht einmal pro Turn, nicht einmal pro abgeschlossener Aufgabe.

Jede Gruppe enthält einen matcher. Dieser legt fest, bei welchen Vorkommnissen der Hook ausgeführt wird. Bei Tool-Ereignissen filtert er nach dem Toolnamen. Daher wird "Edit|Write" bei Dateiänderungen ausgelöst, bei keinen anderen Vorgängen. Matcher unterscheiden zwischen Groß- und Kleinschreibung. Ein leerer Matcher wird bei jedem Vorkommnis ausgelöst. Tools von einem MCP-Server (Model Context Protocol) werden als mcp__<server>__<tool> bezeichnet. Ein Matcher mit dem Wert "mcp__github__.*" erfasst daher die Tools eines Servers und lässt die übrigen Server unberücksichtigt.

Stop-Hooks haben eine Besonderheit, die Sie vor dem Schreiben eines Hooks kennen sollten. Ein blockierender Stop-Hook schickt das Modell zurück zur Bearbeitung. Nach acht aufeinanderfolgenden Blockierungen setzt Claude Code den Hook außer Kraft. Lesen Sie das Feld stop_hook_active aus den Hook-Eingabedaten und beenden Sie den Hook mit dem Exit-Code 0, wenn der Wert true ist. Andernfalls läuft der Hook bis zu dieser Obergrenze weiter.

Was ein Hook über stdin empfängt

Wenn Claude npm test ausführen soll, liest ein PreToolUse-Hook auf Bash diese Daten über stdin:

{
  "session_id": "abc123",
  "cwd": "/home/deploy/myproject",
  "hook_event_name": "PreToolUse",
  "tool_name": "Bash",
  "tool_input": {
    "command": "npm test"
  }
}

Jedes Ereignis enthält session_id, cwd, permission_mode, transcript_path und hook_event_name. Tool-Ereignisse enthalten zusätzlich tool_name, tool_input und tool_use_id. Andere Ereignisse haben eigene Felder: UserPromptSubmit enthält den Text prompt, und SessionStart enthält ein source aus startup, resume, clear, compact oder fork.

jq ist die übliche Methode, diese Daten in einem Shell-Skript zu lesen. In einem minimalen Server-Image ist es nicht vorhanden. Installieren Sie es unter Ubuntu und Debian zuerst mit sudo apt install -y jq.

Was der Exit-Status für den laufenden Tool-Aufruf bewirkt

Es gibt drei Ergebnisse.

  • Exit 0 bedeutet, dass Ihr Hook keinen Einwand erhebt. Bei PreToolUse ist das nicht gleichbedeutend mit einer Genehmigung, und der normale Berechtigungsablauf wird weiterhin ausgeführt. Bei UserPromptSubmit und SessionStart wird stdout zum Kontext des Modells hinzugefügt.
  • Exit 2 blockiert die Aktion bei Ereignissen, die blockiert werden können, darunter PreToolUse, und stderr wird als Begründung angezeigt. Bei Ereignissen, die nicht blockiert werden können, etwa PostToolUse, wird die Blockierung ignoriert. stderr wird dem Modell jedoch weiterhin als Feedback übermittelt.
  • Jeder andere Exit-Code ist ein nicht blockierender Fehler. Die Aktion wird ausgeführt. Das Transkript zeigt einen Hinweis auf den Hook-Fehler. Dieser enthält die erste Zeile von stderr nach dem Text Failed with non-blocking status code:.

Wenn Sie mehr als nur blockieren oder still bleiben möchten, verwenden Sie Exit 0 und geben Sie stattdessen ein JSON-Objekt nach stdout aus. Ein PreToolUse-Hook entscheidet mit permissionDecision:

{
  "hookSpecificOutput": {
    "hookEventName": "PreToolUse",
    "permissionDecision": "deny",
    "permissionDecisionReason": "Database drops go through a migration, not through the agent."
  }
}

"allow" überspringt die interaktive Eingabeaufforderung, "deny" bricht den Aufruf ab und sendet die Begründung an das Modell, und "ask" zeigt die Eingabeaufforderung wie gewohnt an. Verwenden Sie pro Hook nur einen Stil. Wenn Sie Exit 2 mit einer JSON-Entscheidung auf stdout kombinieren, erhalten Sie ein Ergebnis, das Sie nachschlagen müssen.

Wenn mehrere Hooks zu einem Ereignis passen, werden sie parallel ausgeführt, und jeder Hook läuft bis zum Abschluss. Ein deny von einem Hook beendet seine Geschwister-Hooks nicht. Daher schreibt ein Logging-Hook seine Zeile weiterhin, während ein Guardrail-Hook denselben Aufruf ablehnt. Claude Code führt die Antworten anschließend zusammen und behält die restriktivste Antwort in dieser Reihenfolge bei: deny, defer, ask, allow.

Beispiel 1: Einen destruktiven Befehl vor der Ausführung blockieren

Speichern Sie dies als .claude/hooks/block-destructive.sh in Ihrem Projekt:

#!/bin/bash
# Deny a Bash tool call whose command matches a banned pattern.
INPUT=$(cat)
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty')

for pattern in 'rm -rf /' 'mkfs' 'dd if=' 'DROP TABLE'; do
  if printf '%s' "$COMMAND" | grep -qiF -- "$pattern"; then
    echo "Blocked by policy: the command matches '$pattern'. A human runs this one." >&2
    exit 2
  fi
done

exit 0

Machen Sie die Datei ausführbar und registrieren Sie sie anschließend unter PreToolUse in .claude/settings.json:

chmod +x .claude/hooks/block-destructive.sh
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/block-destructive.sh",
            "timeout": 10,
            "statusMessage": "Checking the command against policy"
          }
        ]
      }
    ]
  }
}

Testen Sie das Skript manuell, bevor Sie ihm vertrauen. Ein Hook, der bei seiner eigenen Eingabe abstürzt, lässt die Ausführung zu:

echo '{"tool_name":"Bash","tool_input":{"command":"rm -rf /var/lib/postgresql"}}' \
  | .claude/hooks/block-destructive.sh
echo $?

Auf stderr sollte die Zeile Blocked by policy: erscheinen. Der Exit-Code sollte 2 sein. Übergeben Sie einen harmlosen Befehl wie ls -la. Dann sollte keine Ausgabe erscheinen und der Exit-Code 0 sein. In einer Sitzung erscheint der abgelehnte Aufruf mit Ihrer Nachricht als Begründung im Transkript. Das Modell liest diese Nachricht und passt sein Verhalten an.

Eine Eigenschaft macht dieses Vorgehen besonders nützlich: PreToolUse-Hooks werden in jedem Berechtigungsmodus vor der Prüfung des Berechtigungsmodus ausgeführt. Eine Ablehnung bleibt daher auch unter bypassPermissions wirksam. Dadurch ist ein Hook eine sinnvolle Ergänzung zu dem Auto-Modus von Claude Code und seinen Berechtigungseinstellungen. Die Eingabeaufforderungen werden dabei reduziert, der Hook wird aber weiterhin ausgeführt.

Seien Sie sich über die Grenzen dieses Verfahrens im Klaren. Die Mustererkennung in einer Befehlszeichenfolge schützt davor, dass ein Agent unachtsam handelt. Sie stellt jedoch keine Grenze gegen einen geschickten Agenten dar, weil derselbe Befehl in einer Form geschrieben werden kann, die Ihr grep nicht erkennt. Verbindliche Regeln gehören in das Berechtigungssystem und in das Benutzerkonto, unter dem der Prozess ausgeführt wird.

Beispiel 2: Nach jeder Änderung formatieren und auf Fehler prüfen

PostToolUse mit einem Edit|Write-Matcher wird nach jedem Tool zur Dateibearbeitung ausgeführt. Speichern Sie dies als .claude/hooks/after-edit.sh:

#!/bin/bash
# Format the edited file, then report lint failures back to the model.
INPUT=$(cat)
FILE=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')
[ -z "$FILE" ] && exit 0

case "$FILE" in
  *.py)
    ruff format "$FILE" >/dev/null 2>&1
    if ! ruff check "$FILE" >&2; then
      exit 2
    fi
    ;;
  *.sh)
    if ! shellcheck "$FILE" >&2; then
      exit 2
    fi
    ;;
esac

exit 0
{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/after-edit.sh",
            "timeout": 60
          }
        ]
      }
    ]
  }
}

Bitten Sie Claude, eine schlecht eingerückte Funktion in eine Python-Datei einzufügen, und öffnen Sie anschließend die Datei. Sie wird formatiert angezeigt. Daran erkennen Sie, dass der Hook ausgeführt wurde, denn ein erfolgreicher Hook erzeugt keine Ausgabe in der Unterhaltung.

Der Exit-Code 2 macht hier keine Änderungen rückgängig. PostToolUse wird erst ausgeführt, nachdem das Tool bereits ausgeführt wurde. Die Änderung befindet sich daher in jedem Fall auf der Festplatte. Der Vorteil von Exit-Code 2 besteht darin, dass die Ausgabe von ruff check als Feedback an das Modell übermittelt wird. Dadurch korrigiert es den gerade eingeführten Fehler, anstatt fortzufahren. Das ist der Unterschied zwischen einem Lint-Fehler, den Sie erst beim Commit feststellen, und einem Fehler, den der Agent noch im selben Durchlauf behebt.

Hier sind zwei Einschränkungen von Matchern wichtig. Edit|Write erkennt keine Dateien, die durch einen Shell-Befehl geändert wurden. Claude schreibt Dateien jedoch häufig über Bash, sodass diese Lücke relevant ist. Für eine Abdeckung bei jedem Aufruf sollten Sie auch Bash matchen und das Skript geänderte Dateien mit git status --porcelain auflisten lassen. Für eine Abdeckung einmal pro Durchlauf verschieben Sie den Scan stattdessen in einen Stop-Hook.

Beispiel 3: Jeden Tool-Aufruf für die Prüfung protokollieren

Ein leerer Matcher unter PostToolUse wird bei jedem Tool ausgelöst. Wenn der Datensatz statt in eine Datei im Home-Verzeichnis in das Systemjournal geschrieben wird, bleibt er für die eigene Shell des Agenten unzugänglich:

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "",
        "hooks": [
          {
            "type": "command",
            "command": "jq -c '{time: now|todate, session: .session_id, cwd: .cwd, tool: .tool_name, input: .tool_input}' | logger -t claude-code -p local0.info"
          }
        ]
      }
    ]
  }
}

Lesen Sie die Einträge mit journalctl -t claude-code -o cat | tail -n 5 wieder aus. Sie sollten eine JSON-Zeile pro Tool-Aufruf sehen, wobei der neueste Eintrag zuletzt erscheint. Wenn nichts angezeigt wird, wurde der Hook nicht ausgeführt. Der Abschnitt zur Fehlerbehebung weiter unten behandelt diesen Fall.

Fügen Sie denselben Block unter PostToolUseFailure hinzu, um auch fehlgeschlagene Aufrufe zu erfassen. PostToolUse wird nur bei Erfolg ausgelöst, und ein fehlgeschlagener Befehl ist normalerweise der relevante Fall. Der Grund für logger statt für das Anhängen an eine Datei im Home-Verzeichnis ist die Besitzzuordnung: Ein Hook läuft unter demselben Benutzer wie die Shell des Agenten. Alles, woran dieser Benutzer Daten anhängen kann, kann er auch leeren. Das Journal wird von systemd-journald unter einem eigenen Konto geschrieben.

Wie lange ein Hook laufen darf

ChartDefault hook timeout in seconds, by hook type and event
The data behind this chart
[
  {
    "label": "command, http or mcp_tool hook",
    "default_timeout_seconds": 600
  },
  {
    "label": "agent hook",
    "default_timeout_seconds": 60
  },
  {
    "label": "prompt hook",
    "default_timeout_seconds": 30
  },
  {
    "label": "command hook on UserPromptSubmit",
    "default_timeout_seconds": 30
  },
  {
    "label": "command hook on MessageDisplay",
    "default_timeout_seconds": 10
  },
  {
    "label": "any hook on SessionEnd",
    "default_timeout_seconds": 1.5
  }
]

Ein Command-Hook erhält standardmäßig 600 Sekunden. Das entspricht zehn Minuten. Einige Ereignisse verkürzen dieses Zeitfenster deutlich. SessionEnd-Hooks teilen sich ein Budget von 1.5 Sekunden. Die Bereinigung am Sitzungsende muss daher schnell abgeschlossen sein. Wenn Sie timeout für den Hook höher setzen, wird auch dieses gemeinsame Budget entsprechend erhöht, bis maximal 60 Sekunden.

Ein Hook, der sein Zeitlimit erreicht, wird abgebrochen und trifft keine Entscheidung. Bei einer PreToolUse-Schutzregel bedeutet das, dass sie den Aufruf nicht blockiert. Der Tool-Aufruf wird im normalen Berechtigungsablauf fortgesetzt. Halten Sie Schutzregelskripte deshalb klein. Für langsame Arbeiten, auf die niemand wartet, beispielsweise das Weiterleiten eines Logs an einen anderen Ort, setzen Sie "async": true. Der Hook läuft dann im Hintergrund, ohne den Tool-Aufruf aufzuhalten.

Hooks, Regeldateien, Skills und MCP-Server

Vier Dinge werden miteinander verwechselt, weil sie alle das Verhalten eines Agenten verändern. Nur eines davon verhindert, dass es bei einer Empfehlung bleibt.

Eine Regeldatei (CLAUDE.md oder eine Datei unter .claude/rules/) ist Text, der in den Kontext des Modells geladen wird. Sie beeinflusst das Verhalten, erzwingt aber nichts. In einem langen Gespräch, bei einem großen Diff und zusammen mit einer neuen Benutzeranfrage kann eine einzelne Zeile daraus untergehen. Das ist der übliche Grund dafür, dass Agenten die von Ihnen notierten Anweisungen ignorieren.

Ein Skill ist ein Verzeichnis mit Anweisungen und Skripten, das das Modell lädt, wenn es den Skill für relevant hält. Diese Einschätzung ist der Zweck eines Skills und zugleich seine Grenze: Das Modell entscheidet weiterhin selbst. Beide Seiten zeigt ein Skill wie Ponytail, das einen Agenten zu der kleinsten funktionierenden Änderung führt, weil es die Herangehensweise an eine gesamte Aufgabe prägt, was kein Hook leisten könnte, und zwar nur dann, wenn das Modell den Skill lädt.

Ein MCP-Server (Model Context Protocol) stellt dem Modell neue Tools zur Verfügung, die es aufrufen kann. Er erweitert den Bereich, auf den der Agent zugreifen kann. Er veranlasst den Agenten jedoch nicht, irgendetwas davon zu verwenden. Außerdem handelt es sich um einen separaten Prozess, den Sie betreiben müssen. Das ist eine eigene Aufgabe: siehe MCP-Server auf einem VPS betreiben.

Ein Hook ist das einzige der vier Elemente, das ausgeführt wird, ohne dass das Modell dies auswählt. Verwenden Sie eine Regeldatei für eine Präferenz und einen Skill für ein Verfahren, das das Modell befolgen soll, wenn es anwendbar ist. Verwenden Sie einen Hook für den Schritt, der jedes Mal ausgeführt werden muss, oder für etwas, das niemals geschehen darf. Der ausführlichere Vergleich, einschließlich der Frage, wann ein Skill eine Regeldatei übertrifft, steht in dem Vergleich von Skills, MCP und Regeldateien.

Ein Plugin ist keine fünfte Mechanik, sondern eine Form der Paketierung. Es bündelt Hooks und Skills in einer installierbaren Einheit. So kann ein Team dieselbe Schutzmaßnahme auf allen Rechnern bereitstellen: siehe wie Claude-Code-Plugins funktionieren.

Die Sicherheitsentscheidung bei einem gemeinsam genutzten VPS

Ein Hook ist Code, den der Agent ausführt. Er läuft als der Benutzer, der Claude Code gestartet hat. Er übernimmt die Umgebung und Dateiberechtigungen dieses Benutzers. Auf einem Laptop ist das eine Frage des Arbeitsablaufs. Auf einem VPS, auf dem ein Agent unbeaufsichtigt läuft, ist es eine Sicherheitsfrage mit vier praktischen Aspekten.

Ein Hook in einem Repository ist Code, den Sie nicht selbst geschrieben haben. .claude/settings.json ist versioniert. Daher können beim Klonen eines Repositorys und beim Starten einer Sitzung darin Hooks registriert werden, die mit dem Repository ausgeliefert wurden. Claude Code stellt Projekt-Hooks für diesen Ordner erst nach einer Bestätigung im Dialog zur Arbeitsbereichsvertrauenswürdigkeit frei. Mit dem Akzeptieren der Vertrauensstellung entscheiden Sie also, diese Hooks auszuführen. Lesen Sie zuerst den Block hooks.

Ein Hook sieht die vollständige Werkzeugeingabe. Ein Audit-Hook, der tool_input protokolliert, schreibt jedes Argument jedes Befehls in eine Datei. Dazu kann auch ein Token gehören, das zufällig in der Befehlszeile stand. Dieses Protokoll muss dann genauso geschützt werden wie das Geheimnis selbst. Das ist Teil des größeren Problems, Geheimnisse außerhalb der Reichweite eines KI-Agenten zu halten.

Ein Hook kann in den Kontext des Modells schreiben. Alles, was ein SessionStart- oder UserPromptSubmit-Hook auf stdout ausgibt, wird der Unterhaltung hinzugefügt. Ein Hook, der Text aus einem externen System, einem Issue-Tracker oder einer Logdatei einliest, übergibt dem Modell nicht vertrauenswürdigen Text, als hätten Sie ihn selbst eingegeben. Behandeln Sie diese stdout-Ausgabe als Eingabe und nicht als Ausgabe.

Die Berechtigung ist die tatsächliche Kontrolle. Führen Sie den Agenten als dedizierten, nicht privilegierten Benutzer aus, der nur die benötigten sudo-Regeln besitzt. Eine PreToolUse-Verweigerung ist sinnvoll. Sie ist konstruktionsbedingt nur best effort: Die Referenz sagt dasselbe über den if-Filter und verweist auf das Berechtigungssystem, wenn Sie eine strikt durchgesetzte Verweigerung benötigen. Die Berechtigungsregeln und das Benutzerkonto, unter dem der Prozess läuft, sind die Bestandteile, die auch unter hoher Belastung greifen.

Eine Eigenschaft gilt in jeder Konfiguration. PreToolUse-Hooks werden in jedem Berechtigungsmodus vor der Prüfung des Berechtigungsmodus ausgeführt. Ein Hook, der deny zurückgibt, blockiert das Werkzeug daher auch unter bypassPermissions. Hooks können die durch die Berechtigungsregeln erlaubten Aktionen weiter einschränken. Sie können diese Regeln nicht lockern.

Warum wird mein Hook nicht ausgelöst?

Arbeiten Sie diese Punkte der Reihe nach durch. Jeder Schritt nennt das Symptom, das tatsächlich auftritt.

  • Führen Sie /hooks aus und prüfen Sie, ob der Hook unter dem erwarteten Ereignis angezeigt wird. Ein Hook, der im Menü fehlt, weist meist auf einen JSON-Syntaxfehler in der Einstellungsdatei hin, weil nachgestellte Kommas und Kommentare nicht zulässig sind. Möglich ist auch, dass sich die Datei nicht an einem der sechs oben genannten Speicherorte befindet.
  • Vergleichen Sie den Matcher exakt mit dem Namen des Tools. Bei Matchern wird zwischen Groß- und Kleinschreibung unterschieden. Daher entspricht "bash" niemals dem Tool Bash.
  • Führen Sie das Skript wie in Beispiel 1 oben manuell mit einer Beispiel-Eingabe aus. Ein unerwarteter Exit-Code weist auf einen Fehler in Ihrem Skript hin. Claude Code meldet ihn als Hook-Fehler und nicht als Entscheidung.
  • Die Meldung jq: command not found bedeutet, dass jq auf diesem Rechner fehlt. Ein command not found für Ihr eigenes Skript bedeutet, dass der Pfad nicht aufgelöst werden konnte. Verwenden Sie daher ${CLAUDE_PROJECT_DIR} oder einen absoluten Pfad. Wenn das Skript überhaupt nicht ausgeführt wird, ist es wahrscheinlich nicht ausführbar.
  • Der Hook gibt gültiges JSON aus, aber es passiert nichts. Ein Hook in Shell-Form wird über sh -c ausgeführt. Wenn Ihr Shell-Profil ein Banner ausgibt, wird dieses Banner Ihrem JSON vorangestellt. Die Standardausgabe beginnt dann nicht mehr mit {. Claude Code interpretiert die gesamte Ausgabe als einfachen Text und ignoriert die Entscheidung. Bei Exit 0 wird nirgendwo etwas gemeldet, außer im Debug-Log. Umschließen Sie jedes echo in Ihrem Profil so, dass es nur in interaktiven Shells ausgeführt wird.
  • Wenn das Problem weiterhin besteht, starten Sie die Sitzung mit claude --debug-file /tmp/claude.log und führen Sie tail -f /tmp/claude.log in einem zweiten Terminal aus. Das Debug-Log zeichnet auf, welche Hooks übereinstimmten, welchen Exit-Code die einzelnen Hooks zurückgaben und was sie auf stdout und stderr ausgegeben haben.

FAQ

Was ist der Unterschied zwischen einem Claude-Code-Hook und einer CLAUDE.md-Anweisung?

Eine CLAUDE.md-Anweisung ist Text im Kontext des Modells. Sie konkurriert daher mit der Konversation und der aktuellen Anfrage um Aufmerksamkeit, und das Modell kann sie gegen diese abwägen. Ein Hook ist ein Shell-Befehl, den Claude Code an einem festen Punkt seines Lebenszyklus ausführt. Er wird daher bei jedem Auftreten seines Ereignisses ausgeführt, unabhängig von der Entscheidung des Modells. Verwenden Sie eine Anweisung für eine Präferenz. Verwenden Sie einen Hook für einen Schritt, der immer erfolgen muss, oder für eine Aktion, die niemals erfolgen darf.

Wie verhindere ich, dass Claude Code einen bestimmten Shell-Befehl ausführt?

Registrieren Sie einen PreToolUse-Hook mit einem Bash-Matcher, der den Befehl aus .tool_input.command liest, eine Begründung nach stderr schreibt und mit Status 2 beendet wird. Claude Code bricht den Aufruf ab und zeigt dem Modell Ihre Begründung. Dies geschieht vor der Prüfung des Berechtigungsmodus, sodass die Verweigerung auch im bypassPermissions-Modus gilt. Die Mustererkennung für eine Befehlszeichenfolge ist eine Schutzmaßnahme und keine Sicherheitsgrenze, weil derselbe Befehl in einer Form geschrieben werden kann, die das Muster nicht erfasst. Ergänzen Sie sie daher durch Berechtigungsregeln und ein nicht privilegiertes Konto.

Mein Hook gibt gültiges JSON aus, aber nichts geschieht. Warum?

Die häufigste Ursache ist Ihr Shell-Profil. Ein Hook ohne ein args-Feld wird über sh -c ausgeführt. Einige Profile geben bei jeder Shell ein Banner aus, das vor Ihrem JSON auf stdout gelangt. Da die Ausgabe dann nicht mehr mit { beginnt, behandelt Claude Code sie vollständig als einfachen Text und ignoriert die Entscheidung. Bei Exit-Status 0 wird im Transkript überhaupt nichts gemeldet. Sichern Sie jedes echo in Ihrem Profil mit einer Prüfung auf eine interaktive Shell ab. Bestätigen Sie die Behebung anschließend, indem Sie das Debug-Log aus claude --debug-file /tmp/claude.log lesen.

Ist es sicher, Claude-Code-Hooks auf einem gemeinsam genutzten Server auszuführen?

Hooks werden mit dem Benutzerkonto ausgeführt, das Claude Code gestartet hat, und verfügen über die Dateiberechtigungen dieses Benutzers. Ein Hook kann daher alles tun, was dieses Konto tun darf. Zwei Maßnahmen decken den größten Teil des Risikos ab: Führen Sie den Agenten mit einem dedizierten, nicht privilegierten Konto und einer eng gefassten sudo-Richtlinie aus. Lesen Sie außerdem den hooks-Block jedes Repositorys, bevor Sie dessen Dialog zur Vertrauensstellung des Arbeitsbereichs bestätigen, weil sich Projekt-Hooks in .claude/settings.json befinden. Setzen Sie "disableAllHooks": true in Ihrer Einstellungsdatei, wenn keiner dieser Hooks ausgeführt werden soll.