devAlice
← AI Agents

Claude Code Hooks — Shell-Befehle automatisch bei Sitzungs- und Tool-Ereignissen ausführen

Hooks in settings.json fügen Shell-Befehle bei Sitzungsstart, vor/nach Tool-Aufrufen und beim Sitzungsende ein. Fünf Beispiele: Sicherheitsblockierung, Auto-Formatierung, Logging, externe Benachrichtigungen.

Claude Codes Hooks — definiert unter dem hooks-Schlüssel in settings.json — ermöglichen es, Shell-Befehle automatisch bei Sitzungsstart, vor und nach Tool-Aufrufen sowie beim Sitzungsende auszuführen. So lässt sich abfangen, was die KI als Nächstes tun wird, im Nachgang aufräumen oder am Ende bereinigen. Ein leistungsstarker Erweiterungspunkt.

Ich denke, was den Hook-Mechanismus so wirkungsvoll macht, ist nicht die Automatisierung selbst, sondern die Fähigkeit, Verhalten durchzusetzen — weil ein PreToolUse-Hook, der gefährliche Befehle blockiert, zuverlässiger ist als jede Erinnerung. Früher musste man solche Sicherheitsprüfungen manuell vornehmen; heute lassen sie sich als Code formalisieren, statt als Konvention zu existieren.

Dieser Leitfaden richtet sich an Claude Code 1.x und die offizielle Hooks-Spezifikation. Er baut auf Claude Code einrichten auf und fügt eine weitere Automatisierungs- und Sicherheitsebene hinzu.

TL;DR

Hook-EreignisWird ausgelöstTypische Verwendung
SessionStartDirekt nach SitzungsbeginnSpeicher-Sync-Hinweis, Umgebungsvalidierung
UserPromptSubmitDirekt nach Eingabe eines PromptsSlash-Command-Erkennung, Logging, Nachbearbeitung
PreToolUseDirekt vor einem Tool-AufrufGefährliche Befehle blockieren, Secret-Dateien schützen
PostToolUseDirekt nach einem Tool-AufrufAuto-Formatierung (prettier), Lint, Nebeneffekt-Logging
StopDirekt vor SitzungsendeÄnderungszusammenfassung, Backup, Benachrichtigung

Wichtige Regeln:

  • Exit-Code 2 blockiert den Tool-Aufruf (nur bei PreToolUse).
  • Der JSON-Payload wird per stdin übergeben — mit jq parsen.
  • Hooks sind Shell-Befehle — auf Unterschiede zwischen Betriebssystemen achten (macOS/Linux/Windows WSL).

Voraussetzungen

  • Claude Code 1.x installiert — Claude Code einrichten
  • jq installiert (brew install jq / apt install jq / winget install jqlang.jq)
  • Schreibzugriff auf settings.json (üblicherweise ~/.claude/settings.json)

Basis-Konfiguration herunterladen

Die von devAlice empfohlene Fünf-Hook-Basis inklusive Deny/Allow-Liste — herunterladen und an die eigene Umgebung anpassen.

settings.json
# 1. Herunterladen
curl -fsSL https://devalice.jaceclub.com/assets/ai-agents/claude-code-hooks/settings.example.json -o settings.json
 
# 2. SHA-256 prüfen
shasum -a 256 settings.json
# Erwartet: 30c1d4117a935cac03b601c71125c11b71733c8c59517ab129fd8913cfb8a69e
 
# 3. Prüfen (insbesondere PreToolUse-Hooks)
less settings.json
 
# 4. An die richtige Stelle kopieren (macOS/Linux)
mkdir -p ~/.claude
cp settings.json ~/.claude/settings.json

⚠️ Achtung: Hooks führen beliebige Shell-Befehle aus. Immer prüfen, bevor fremde Hooks übernommen werden. Dieses Beispiel ist eine sichere Fünf-Hook-Basis — vor dem Einsatz dennoch für die eigene Umgebung anpassen.


1. Hook-Struktur — 5 Min.

Der hooks-Schlüssel in ~/.claude/settings.json:

{
  "hooks": {
    "EventName": [
      {
        "matcher": "Regex oder Tool-Name",  // optional
        "hooks": [
          {
            "type": "command",
            "command": "/bin/bash -lc 'eigentlicher Shell-Befehl'"
          }
        ]
      }
    ]
  }
}

Matching-Regeln:

  • Kein matcher → löst bei jedem Ereignis aus
  • matcher = Tool-Name (Bash, Write, Edit, Read …) → nur bei diesem Tool
  • matcher = Regex (^/(?:ej|euijinpro)\\b) → für UserPromptSubmit, gleicht den Prompt-Text ab

Per stdin eingehender JSON-Payload:

{
  "session_id": "...",
  "transcript_path": "/path/to/transcript.jsonl",
  "cwd": "/Users/...",
  "tool_name": "Bash",
  "tool_input": { "command": "..." },
  "tool_response": { /* nur PostToolUse */ }
}

Mit jq -r .tool_input.command extrahieren.


2. SessionStart — Kontext laden

Der einfachste Hook. Eine Zeile ausgeben, sobald die Sitzung beginnt:

"SessionStart": [
  {
    "hooks": [
      {
        "type": "command",
        "command": "/bin/bash -lc 'printf \"[Sitzungsstart] %s\\nSpeicher-Sync: aktuell (übersprungen)\\n\" \"$(date \"+%Y-%m-%d %H:%M:%S\")\"'"
      }
    ]
  }
]

Die Stdout-Ausgabe wird als System-Nachricht angezeigt. Typische Verwendungsmöglichkeiten:

  • Automatisches Laden von Speicherdateien auslösen
  • Projektspezifischen Kontext ausgeben
  • Git-Status-Zusammenfassung zu Sitzungsbeginn
# Erweitertes Beispiel
command='/bin/bash -lc "echo [Sitzungsstart]; git -C \"$PWD\" status -s; git -C \"$PWD\" log --oneline -3"'

3. PreToolUse — Gefährliche Befehle blockieren (wertvollster Hook)

exit 2 blockiert den Aufruf. Selbst wenn die KI einen gefährlichen Shell-Befehl ausführen möchte, wird er gestoppt.

3.1 Gefährliche Befehlsmuster

"PreToolUse": [
  {
    "matcher": "Bash",
    "hooks": [
      {
        "type": "command",
        "command": "/bin/bash -lc 'cmd=$(jq -r .tool_input.command); case \"$cmd\" in *\"rm -rf /\"*|*\"sudo rm\"*|*\":(){\"*) echo \"gefährlicher Befehl blockiert: $cmd\" >&2; exit 2;; esac'"
      }
    ]
  }
]

Blockierte Muster:

  • rm -rf / — Systemzerstörung
  • sudo rm — unkontrolliertes Löschen (bei Bedarf per Allow-List freigeben)
  • :(){ — Fork-Bombe (:(){ :|: & };:)

3.2 Bearbeitungen sensibler Dateien blockieren

{
  "matcher": "Write|Edit",
  "hooks": [
    {
      "type": "command",
      "command": "/bin/bash -lc 'path=$(jq -r .tool_input.file_path); case \"$path\" in */.env|*/credentials.json|*/.aws/credentials) echo \"Secret-Datei-Bearbeitung blockiert: $path\" >&2; exit 2;; esac'"
    }
  ]
}

Verhindert versehentliche Bearbeitungen von .env, credentials.json, ~/.aws/credentials und ähnlichen Secret-Dateien.

3.3 Bestimmte Verzeichnisse schützen

command='/bin/bash -lc "path=\$(jq -r .tool_input.file_path); case \"\$path\" in /etc/*|/System/*) echo \"Systempfad blockiert: \$path\" >&2; exit 2;; esac"'

4. PostToolUse — Auto-Formatierung / Lint / Log

Aufräumen nach dem Tool-Aufruf.

4.1 Prettier-Auto-Formatierung

"PostToolUse": [
  {
    "matcher": "Edit|Write|MultiEdit",
    "hooks": [
      {
        "type": "command",
        "command": "/bin/bash -lc 'path=$(jq -r .tool_input.file_path); case \"$path\" in *.ts|*.tsx|*.js|*.jsx) prettier --write \"$path\" 2>/dev/null || true ;; esac'"
      }
    ]
  }
]

Nach jedem KI-Write/Edit formatiert prettier automatisch nach. Das kostet ca. 0,5 s pro Bearbeitung.

4.2 Geänderte Dateien aufzeichnen

command='/bin/bash -lc "path=\$(jq -r .tool_input.file_path); echo \"[geändert] \$(date +%T) \$path\" >> ~/.claude/change.log"'

Erstellt eine zeitgeordnete Aufzeichnung jeder Dateiänderung in der Sitzung.

4.3 Typ-Prüfung / Lint (aufwändige Aufgaben in den Stop-Hook verschieben)

tsc --noEmit bei jeder Bearbeitung ist zu teuer. Besser im Stop-Hook (§6) bündeln.


5. UserPromptSubmit — Slash-Command-Erkennung / Nachbearbeitungs-Trigger

"UserPromptSubmit": [
  {
    "matcher": "^/(?:ej|euijinpro)\\b",
    "hooks": [
      {
        "type": "command",
        "command": "/bin/bash -lc 'echo \"[ej skill] Prompt für euijin.pro-Logging vorgemerkt\" >&2'"
      }
    ]
  }
]
  • Slash-Commands erkennen und externe Systeme auslösen (Slack, separates Logging)
  • Bestimmte Schlüsselwörter automatisch markieren (urgent, bug)
  • Token-Budget-Schwellenwerte prüfen

6. Stop — Aufräumen am Sitzungsende

"Stop": [
  {
    "hooks": [
      {
        "type": "command",
        "command": "/bin/bash -lc 'printf \"\\n[Sitzungsende] %s\\n\" \"$(date \"+%Y-%m-%d %H:%M:%S\")\"'"
      }
    ]
  }
]

Typische Verwendungsmöglichkeiten:

  • Git-Status-Zusammenfassung geänderter Dateien
  • Backup (rsync oder git stash)
  • Externe Shutdown-Benachrichtigung
  • Einmalige Typ-Prüfung / Build
command='/bin/bash -lc "if git diff --quiet; then echo \"[sauber]\"; else echo \"[Änderungen]\"; git diff --stat; fi"'

7. permissions — Deny-List / Allow-List

Von Hooks getrennt, aber der wichtigste Sicherheitsschlüssel in settings.json.

"permissions": {
  "deny": [
    "Bash(rm -rf /:*)",
    "Bash(sudo rm:*)",
    "Read(.env)",
    "Read(.env.local)",
    "Read(.aws/credentials)",
    "Read(*/credentials.json)"
  ],
  "allow": [
    "Bash(git status:*)",
    "Bash(git log:*)",
    "Bash(git diff:*)",
    "Bash(pnpm:*)",
    "Bash(npm:*)",
    "Bash(node:*)"
  ]
}
  • deny gewinnt bedingungslos — ergänzt die Hook-Blockierungen
  • allow genehmigt automatisch ohne Rückfrage (für häufige, sichere Befehle)
  • Alles andere löst jedes Mal eine Benutzerabfrage aus

Die Hooks in diesem Leitfaden überschneiden sich mit deny — Defense in Depth.


8. settings.json — Speicherorte und Priorität

SpeicherortGeltungsbereichHinweise
~/.claude/settings.jsonBenutzer-globalGilt für alle Projekte
<project>/.claude/settings.jsonProjektWird beim Projektstart mit den Benutzereinstellungen zusammengeführt
<project>/.claude/settings.local.jsonProjekt + benutzerlokalEmpfehlung: in .gitignore aufnehmen

Zusammenführungsreihenfolge: Benutzer → Projekt → Projekt.local. Spätere Einstellungen überschreiben frühere.

Hooks werden angehängt, nicht zusammengeführt — 5 Benutzer-Hooks + 3 Projekt-Hooks = alle 8 laufen.


9. Debugging — wenn der Hook nicht auslöst

9.1 stdin-Payload prüfen

Temporärer Debug-Hook:

{
  "matcher": "Bash",
  "hooks": [
    {
      "type": "command",
      "command": "/bin/bash -lc 'cat > /tmp/claude-hook-debug.json'"
    }
  ]
}

cat /tmp/claude-hook-debug.json | jq .

9.2 Exit-Codes testen

command='/bin/bash -lc "echo dbg >&2; exit 2"'

→ Prüfen ob Blockierung (PreToolUse) funktioniert.

9.3 Hook-Auslösung bestätigen

command='/bin/bash -lc "echo \"[Hook ausgelöst $$ $(date +%T)]\" >> ~/.claude/hook-trace.log"'

Log nach einer Sitzung prüfen.

9.4 JSON-Syntaxfehler

settings.json vergibt keine Fehler — fehlende Kommas und übersehene Escapes sind häufige Fallen. Mit jq . ~/.claude/settings.json validieren.


10. Fehlerbehebung

„command not found: jq"

brew install jq (macOS) / sudo apt install jq (Linux) / winget install jqlang.jq (Windows).

Hook löst aus, tut aber nichts

  • command muss einzeilig sein. Für mehrzeilige Befehle \\n verwenden oder ein separates Skript aufrufen.
  • Shell-Escapes beachten — \" ist innerhalb von JSON doppelt escaped.

PreToolUse exit 2 wird ignoriert

  • Sicherstellen, dass exit 2 tatsächlich die letzte Zeile des Shell-Befehls erreicht.
  • In einer set -e-Umgebung kann case ;; mit exit 0 enden — stattdessen exit 2 direkt im gematchten Branch setzen.

Prettier läuft nicht

  • Prüfen, ob prettier im Projektstamm installiert ist (pnpm prettier --version).
  • Der Hook-PATH enthält möglicherweise kein node_modules/.binnpx prettier oder den absoluten Pfad verwenden.

Änderungen an settings.json werden nicht übernommen

  • Ein Sitzungsneustart ist erforderlich. Alternativ /config reload (falls unterstützt).
  • Bei JSON-Syntaxfehlern wird der Hook-Abschnitt lautlos deaktiviert. Mit jq . validieren.

Nächste Schritte

Referenzen

Changelog

  • 2026-05-12 — Erster Entwurf (devAlice M2 Seed-Erweiterung)