SSD Nodes Learn Hosting plans →
Anleitungen Matt ConnorVon Matt Connor · Aktualisiert 2026-08-24

Claude Code Hooks: Ereignisse, Exit-Code 2 und Sicherheit

Erfahren Sie, wo Claude Code Hooks liegen, welche Ereignisse sie ausloesen und warum Exit-Code 2 einen Tool-Aufruf vor der Ausfuehrung stoppt.

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 Vorgabe, die das Modell gegen alle anderen Informationen in seinem Kontext abwägt. Ein Hook ist Code und wird ausgeführt, unabhängig davon, ob das Modell damit einverstanden ist. Wenn Ihr Agent den Formatter trotz Ihrer zweimaligen Aufforderung weiterhin überspringt, 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 Arbeit aus und antwortet 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, einschließlich dieses Beitrags, kopieren. Geben Sie Ihre Version mit claude --version aus.

Wo die Hook-Konfiguration liegt

Ein Hook ist ein JSON-Block in einer Einstellungsdatei. An sechs Stellen kann ein solcher Block hinterlegt werden. Der Geltungsbereich der Datei bestimmt den Geltungsbereich des Hooks.

  • ~/.claude/settings.json: jedes Projekt auf Ihrem Rechner, aber auf keinem anderen Rechner.
  • .claude/settings.json: ein einzelnes Projekt, in das Repository committed. Jeder, der das Projekt klont, erhält den Hook.
  • .claude/settings.local.json: ein einzelnes Projekt, nur auf Ihrem Rechner.
  • Verwaltete Richtlinieneinstellungen: organisationsweit und von einem Administrator festgelegt.
  • hooks/hooks.json innerhalb eines Plugins: gültig, solange dieses Plugin aktiviert ist.
  • Frontmatter eines Skills oder Subagents: gültig, 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, anstatt sie zu ersetzen. Daher kann ein Ereignis mehrere Hooks aus mehreren Dateien enthalten. Mit "disableAllHooks": true werden sie deaktiviert. Eine Ausnahme gilt: Hooks aus verwalteten Richtlinieneinstellungen laufen weiter, 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. Sie ändern einen Hook, indem Sie die Einstellungsdatei bearbeiten. Der File-Watcher 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 Komprimierung, Subagenten, Worktrees und Konfigurationsdateien ab. Für Serverarbeiten werden einige davon verwendet.

  • 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 Genehmigungsabfrage angezeigt.
  • UserPromptSubmit: wenn Sie eine Eingabe absenden, bevor Claude sie 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 nach einer Komprimierung ebenfalls ausgelöst, dann mit dem Matcher-Wert compact.
  • Stop: wenn Claude die Antwort fertiggestellt hat. Das geschieht einmal pro Turn, nicht einmal pro abgeschlossener Aufgabe.

Jede Gruppe enthält einen matcher. Dieser legt fest, bei welchen Vorkommen der Hook ausgeführt wird. Bei den Tool-Ereignissen filtert er nach dem Toolnamen. Dadurch wird "Edit|Write" bei Dateiänderungen ausgelöst, aber bei keinen anderen Vorgängen. Matcher unterscheiden zwischen Groß- und Kleinschreibung. Ein leerer Matcher wird bei jedem Vorkommen ausgelöst. Tools von einem MCP-Server (model context protocol) heißen mcp__<server>__<tool>. Ein Matcher mit "mcp__github__.*" erfasst daher die Tools eines Servers und lässt die anderen 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 an die Arbeit. Claude Code setzt den Hook nach acht aufeinanderfolgenden Blockierungen außer Kraft. Lesen Sie das Feld stop_hook_active aus der Hook-Eingabe und beenden Sie den Hook mit Exit-Code 0, wenn der Wert true ist. Andernfalls läuft der Hook weiter, bis diese Obergrenze erreicht ist.

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 enthalten eigene Felder: UserPromptSubmit erhält den Text prompt, und SessionStart erhält eine source aus startup, resume, clear, compact oder fork.

jq ist die übliche Methode, um diese Daten in einem Shell-Skript zu lesen. In einem minimalen Server-Image ist dieses Programm 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. stderr wird als Begründung für das Modell angezeigt. Bei Ereignissen, die nicht blockiert werden können, beispielsweise 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. Im Transkript wird ein Hinweis auf den Hook-Fehler angezeigt. Er enthält die erste Zeile von stderr nach dem Text Failed with non-blocking status code:.

Für alles, was über Blockieren oder Stillschweigen hinausgeht, verwenden Sie Exit 0 und geben stattdessen ein JSON-Objekt auf 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, dessen Bedeutung 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 die übrigen Hooks nicht. Ein Logging-Hook schreibt daher weiterhin seine Zeile, während ein Guardrail-Hook denselben Aufruf ablehnt. Claude Code führt die Antworten anschließend zusammen und verwendet die restriktivste Entscheidung in der Reihenfolge deny, defer, ask, allow.

Beispiel 1: Einen destruktiven Befehl blockieren, bevor er ausgeführt wird

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: mit dem Exit-Code 2 erscheinen. Übergeben Sie ihm einen harmlosen Befehl wie ls -la. Dann sollten keine Ausgaben und der Exit-Code 0 erscheinen. In einer Sitzung wird der verweigerte Aufruf mit Ihrer Meldung als Begründung im Transkript angezeigt. Das Modell liest diese Meldung und passt sein Verhalten an.

Eine Eigenschaft macht dieses Vorgehen besonders nützlich: PreToolUse-Hooks werden vor der Prüfung des Berechtigungsmodus ausgeführt, und zwar in jedem Berechtigungsmodus. Eine Verweigerung bleibt daher auch unter bypassPermissions wirksam. Deshalb ist ein Hook eine sinnvolle Ergänzung zu Claude Code im automatischen Modus und seinen Berechtigungseinstellungen. Dort werden die Eingabeaufforderungen reduziert, der Hook wird aber weiterhin ausgeführt.

Sie sollten die Grenzen dieses Verfahrens kennen. Die Musterprüfung einer Befehlszeichenfolge ist eine Schutzmaßnahme gegen unvorsichtiges Verhalten eines Agenten. Sie ist jedoch keine Grenze gegen einen Agenten, der absichtlich eine andere Schreibweise verwendet. Derselbe Befehl kann in einer Form angegeben werden, die Ihr grep nicht erkennt. Verbindliche Regeln gehören in das Berechtigungssystem und in das Konto, unter dem der Prozess ausgeführt wird.

Beispiel 2: Nach jeder Änderung formatieren und 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, einer Python-Datei eine schlecht eingerückte Funktion hinzuzufügen, und öffnen Sie anschließend die Datei. Die Funktion ist bereits formatiert. Daran erkennen Sie, dass der Hook ausgeführt wurde, denn ein erfolgreich ausgeführter Hook erzeugt in der Unterhaltung keine Ausgabe.

Der Exit-Code 2 macht die Änderung hier nicht rückgängig. PostToolUse wird erst ausgeführt, nachdem das Tool bereits abgeschlossen ist. 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 übergeben wird. Dadurch korrigiert es den gerade eingeführten Fehler, statt fortzufahren. Das ist der Unterschied zwischen einem Lint-Fehler, den Sie erst beim Commit finden, und einem Fehler, den der Agent im selben Durchlauf behebt.

Hier sind zwei Einschränkungen von Matchern relevant. Edit|Write erkennt keine Dateien, die durch einen Shell-Befehl geändert wurden. Claude schreibt Dateien außerdem häufig über Bash, sodass diese Lücke relevant ist. Für eine Abdeckung bei jedem Aufruf können Sie zusätzlich auf Bash matchen und das Skript die geänderten Dateien mit git status --porcelain auflisten lassen. Für eine Abdeckung einmal pro Durchlauf setzen Sie die Prüfung stattdessen in einen Stop-Hook.

Beispiel 3: Jeden Tool-Aufruf für Audits 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 unerreichbar:

{
  "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 aus. Pro Tool-Aufruf sollte eine JSON-Zeile erscheinen, wobei der neueste Eintrag zuletzt steht. Wenn nichts angezeigt wird, wurde der Hook nicht ausgeführt. Der folgende Abschnitt zur Fehlerbehebung behandelt diesen Fall.

Fügen Sie denselben Block unter PostToolUseFailure hinzu, um auch fehlgeschlagene Aufrufe zu erfassen. PostToolUse wird nur bei Erfolg ausgelöst, während ein fehlgeschlagener Befehl normalerweise der interessante Fall ist. Der Grund für logger statt des Anhängens an eine Datei im Home-Verzeichnis ist der Besitz: Ein Hook wird unter demselben Benutzer wie die Shell des Agenten ausgeführt. Alles, woran dieser Benutzer Daten anhängen kann, kann er auch leeren. Das Journal wird von systemd-journald unter einem eigenen Benutzerkonto 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. Bei einigen Ereignissen ist das Zeitbudget deutlich kleiner. SessionEnd-Hooks teilen sich ein Budget von 1.5 Sekunden. Die Bereinigung am Sitzungsende muss daher schnell abgeschlossen werden. Wenn Sie für den Hook ein längeres timeout festlegen, erhöht sich dieses gemeinsame Budget entsprechend auf maximal 60 Sekunden.

Ein Hook, der sein Zeitlimit erreicht, wird abgebrochen und trifft keine Entscheidung. Bei einem PreToolUse-Guardrail bedeutet das, dass er den Vorgang nicht blockiert. Der Tool-Aufruf wird mit dem normalen Berechtigungsablauf fortgesetzt. Halten Sie Guardrail-Skripte deshalb klein. Für langsame Aufgaben, auf deren Abschluss niemand wartet, beispielsweise das Weiterleiten eines Logs, 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 Agents ändern. Nur eines davon ist keine bloße Empfehlung.

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 einer langen Unterhaltung, einem großen Diff und zusammen mit einer neuen Benutzeranfrage kann eine einzelne Zeile daraus untergehen. Das ist der übliche Grund dafür, dass Agents 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 zeigen sich an einem Skill wie Ponytail, das einen Agent dazu anhält, die kleinste funktionierende Änderung vorzunehmen, weil es die Herangehensweise an eine gesamte Aufgabe auf eine Weise prägt, die kein Hook leisten könnte, und nur dann, wenn das Modell den Skill lädt.

Ein MCP-Server (Model Context Protocol) stellt dem Modell neue Tools zur Verfügung. Dadurch erweitert sich der Bereich, auf den der Agent zugreifen kann. Das Modell wird dadurch jedoch nicht dazu veranlasst, diese Tools zu verwenden. Außerdem handelt es sich um einen separaten Prozess, den Sie selbst 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 bei passenden Aufgaben befolgen soll. 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 Fälle, in denen ein Skill besser geeignet ist als eine Regeldatei, steht unter der Gegenüberstellung von Skills, MCP und Regeldateien.

Ein Plugin ist eine Verpackungsform und kein fünfter Mechanismus. Es bündelt Hooks und Skills zu einer installierbaren Einheit. So kann ein Team dieselbe Schutzmaßnahme auf allen Maschinen bereitstellen: siehe Funktionsweise von Claude-Code-Plugins.

Die Sicherheitsentscheidung auf 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 die 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 kann das Klonen eines Repositorys und das Starten einer Sitzung darin Hooks registrieren, die mit dem Repository geliefert wurden. Claude Code führt Projekt-Hooks für diesen Ordner erst nach der Bestätigung im Workspace-Trust-Dialog aus. Mit der Bestätigung entscheiden Sie also, dass diese Hooks ausgeführt werden dürfen. Lesen Sie zuerst den Block hooks.

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

Ein Hook kann Inhalte 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 von außerhalb, aus einem Issue-Tracker oder aus einer Logdatei weiterleitet, übergibt dem Modell nicht vertrauenswürdigen Text, als hätten Sie ihn selbst eingegeben. Ein Hook, der eine Notiz aus einer anderen Claude-Code-Sitzung auf demselben VPS weiterleitet, tut dasselbe. Die Ausgabe eines Agenten ist nicht vertrauenswürdiger als die eines Issue-Trackers. Behandeln Sie diese stdout-Ausgabe als Eingabe und nicht als Ausgabe.

Die Berechtigung ist die entscheidende Kontrolle. Führen Sie den Agenten als dedizierten Benutzer ohne privilegierte Rechte aus. Geben Sie ihm nur die sudo-Regeln, die er benötigt. Eine PreToolUse-Deny-Regel ist sinnvoll. Sie ist jedoch absichtlich nur eine Best-Effort-Kontrolle: Die Referenz sagt dasselbe über den if-Filter und empfiehlt, für eine harte Verweigerung das Berechtigungssystem zu verwenden. Die Berechtigungsregeln und das Konto, unter dem der Prozess läuft, sind die Komponenten, die auch unter Belastung zuverlässig greifen.

Eine Eigenschaft gilt in jeder Konfiguration. PreToolUse-Hooks werden in jedem Berechtigungsmodus vor der Prüfung des Berechtigungsmodus ausgeführt. Daher blockiert ein Hook mit dem Rückgabewert deny das Tool 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 in der angegebenen Reihenfolge durch. Jeder Schritt nennt das Symptom, das Sie tatsächlich sehen werden.

  • Führen Sie /hooks aus und prüfen Sie, ob der Hook unter dem erwarteten Ereignis angezeigt wird. Fehlt ein Hook im Menü, enthält die Einstellungsdatei meist einen JSON-Syntaxfehler, weil abschließende Kommas und Kommentare nicht zulässig sind, oder die Datei befindet sich nicht an einem der sechs oben genannten Speicherorte.
  • Vergleichen Sie den Matcher exakt mit dem Namen des Tools. Matcher berücksichtigen die Groß- und Kleinschreibung. Daher passt "bash" niemals zum Tool Bash.
  • Führen Sie das Skript wie im obigen Beispiel 1 mit einer Beispiel-Eingabe manuell aus. Ein unerwarteter Exit-Code ist ein Fehler in Ihrem Skript. Claude Code meldet ihn als Hook-Fehler und nicht als Entscheidung.
  • Ein Hinweis mit dem Text 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. Wird das Skript überhaupt nicht ausgeführt, 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 liest die gesamte Ausgabe als reinen Text und ignoriert die Entscheidung. Bei Exit 0 wird sie nirgendwo außer im Debug-Log gemeldet. Umschließen Sie alle echo in Ihrem Profil so, dass sie nur in interaktiven Shells ausgeführt werden.
  • 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 erfasst, welche Hooks zugeordnet wurden, welchen Exit-Code jeder Hook zurückgegeben hat und alles, was sie auf stdout und stderr geschrieben 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 Unterhaltung und der aktuellen Anfrage um Aufmerksamkeit, und das Modell kann sie gegenüber diesen 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 davon, wie das Modell entschieden hat. Verwenden Sie eine Anweisung für eine Präferenz. Verwenden Sie einen Hook für einen Schritt, der immer ausgeführt werden muss, oder für eine Aktion, die niemals ausgeführt werden 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 Exit-Code 2 beendet wird. Claude Code bricht den Aufruf ab und zeigt dem Modell Ihre Begründung an. Dies geschieht vor der Prüfung des Berechtigungsmodus, sodass die Ablehnung auch im bypassPermissions-Modus gilt. Die Musterprüfung einer 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 passiert. 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. Dieses steht dann in stdout vor Ihrem JSON. Weil die Ausgabe nicht mehr mit { beginnt, behandelt Claude Code sie vollständig als Klartext und ignoriert die Entscheidung. Bei Exit-Code 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 Korrektur 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 verwenden dessen Dateiberechtigungen. Ein Hook kann daher alles tun, wozu dieses Konto berechtigt ist. 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 akzeptieren, weil sich Projekt-Hooks in .claude/settings.json befinden. Setzen Sie "disableAllHooks": true in Ihrer Einstellungsdatei, wenn keiner dieser Hooks ausgeführt werden soll.