Claude Code Hook Events: Wann passiert was?
Hook Events: Der richtige Zeitpunkt für Deine Automatisierung
Claude Code bietet zehn verschiedene Hook Events. Jedes Event entspricht einem bestimmten Zeitpunkt im Arbeitsablauf. Die Kunst liegt darin, den passenden Zeitpunkt für Deine Automatisierung zu wählen.
Hook Events funktionieren wie Türklingeln in einem Gebäude. Jede Klingel hängt an einer anderen Tür und läutet zu einem anderen Zeitpunkt. Wenn Du weißt, welche Tür wann geöffnet wird, kannst Du Deine Aktionen genau dort platzieren, wo sie am meisten Sinn ergeben.
Die zehn Hook Events im Überblick
| Event | Zeitpunkt | Typische Anwendung |
|---|---|---|
| SessionStart | Bei Sitzungsbeginn | Konfiguration laden, Kontext initialisieren |
| UserPromptSubmit | Nach Eingabe, vor Verarbeitung | Eingabe validieren, Kontext anreichern |
| PreToolUse | Vor Tool-Ausführung | Backup, Validierung, Blocking |
| PostToolUse | Nach Tool-Ausführung | Logging, Qualitätsprüfung, Cleanup |
| Stop | Bei Abbruch durch Benutzer | Cleanup, Task-Status aktualisieren |
| SubagentStop | Wenn Subagent beendet | Ergebnisse zusammenführen, Logging |
| SessionEnd | Bei Sitzungsende | Statistiken speichern, Aufräumen |
| Notification | Bei System-Benachrichtigung | Weiterleitung, Logging |
| PermissionRequest | Bei Berechtigungsanfrage | Automatische Genehmigung, Logging |
| PreCompact | Vor Kontext-Komprimierung | Wichtige Infos sichern |
Blocking vs. Non-Blocking Events
Ein wichtiger Unterschied: Manche Events können den Ablauf blockieren, andere nur beobachten.
- Blocking-fähig: PreToolUse, UserPromptSubmit
- Nur beobachtend: PostToolUse, SessionStart, SessionEnd, Stop, etc.
Wenn Du einen Hook für PreToolUse schreibst, kann dieser die Tool-Ausführung verhindern. Bei PostToolUse ist die Ausführung bereits passiert, hier kannst Du nur noch reagieren.
Welches Event für welchen Zweck?
Faustregel: Willst Du etwas verhindern, nutze ein Pre-Event. Willst Du etwas dokumentieren oder nachbearbeiten, nutze ein Post-Event oder SessionEnd.
SessionStart: Der Startschuss
Kennst Du das Gefühl, wenn Du morgens ins Büro kommst und als Erstes den Rechner hochfährst, Kaffee holst, Deinen Kalender checkst? Genau das ist SessionStart: Der Moment, in dem alles beginnt und ich mich auf den Tag vorbereite.
Ein Sprinter kniet im Startblock. Die Muskeln gespannt, die Konzentration maximal, alle Systeme auf „bereit". Der Schuss ertönt. Und genau das ist SessionStart: Der Moment, in dem der Schuss fällt und die Arbeit beginnt.
Wann wird SessionStart ausgelöst?
SessionStart feuert exakt einmal pro Sitzung. Nicht bei jedem Prompt, nicht bei jeder Aktion. Einmal. Ganz am Anfang. Wenn Du claude in Deinem Terminal eingibst und Enter drückst, dann passiert folgendes:
- Claude Code startet
- Die Konfiguration wird geladen
- SessionStart feuert
- Alle SessionStart-Hooks werden ausgeführt
- Die Sitzung beginnt
Das ist der Moment, in dem ich alles vorbereiten kann, was für die gesamte Sitzung gelten soll. Einmal einrichten, dann läuft es.
Der Unterschied macht's
Warum ist das wichtig? Weil SessionStart nur einmal feuert, ist es der perfekte Ort für Dinge, die ich nicht bei jeder einzelnen Aktion wiederholen will. Datenbankverbindungen prüfen, Konfigurationen laden, den Kontext aufbauen. Alles, was „einmal am Anfang" passieren soll, gehört hierher.
Was bekomme ich im Hook zu sehen?
Die Daten, die ein SessionStart-Hook erhält, sind überschaubar aber wertvoll. Ich bekomme alles, was ich brauche, um die Sitzung einzuordnen.
JSON: SessionStart Datenstruktur
{
"event": "SessionStart",
"session_id": "abc123-def456-ghi789...",
"timestamp": "2026-01-13T09:00:00Z",
"working_directory": "/var/www/mein-projekt",
"user": "entwickler"
}
Ich sehe also:
- session_id: Eine eindeutige ID für diese Sitzung. Nützlich für Protokollierung und Nachverfolgung.
- working_directory: In welchem Verzeichnis arbeiten wir? Das verrät mir das Projekt.
- user: Welcher Benutzer hat die Sitzung gestartet?
- timestamp: Wann genau hat die Sitzung begonnen?
Mit dem working_directory kann ich zum Beispiel projektspezifische Regeln laden. Arbeite ich in /var/www/shop? Dann gelten andere Coding-Standards als in /var/www/blog.
Praxisbeispiel 1: Contract Loader
Mein Lieblings-Anwendungsfall: Projektspezifische Regeln automatisch laden. Jedes meiner Projekte hat eigene Coding-Standards, eigene Architektur-Entscheidungen, eigene „Das machen wir so"-Vereinbarungen. Die will ich nicht bei jedem Prompt neu erklären.
Also lade ich sie einmal am Anfang:
Python: Projektspezifische Contracts laden
#!/usr/bin/env python3
"""SessionStart Hook: Lädt projektspezifische Contracts"""
import json
import sys
from pathlib import Path
def lade_projekt_contracts(arbeitsverzeichnis):
"""Sucht und lädt Contracts für dieses Projekt"""
contracts = []
contract_pfad = Path(arbeitsverzeichnis) / '.contracts'
if contract_pfad.exists():
for datei in contract_pfad.glob('*.yaml'):
contracts.append(datei.read_text())
return contracts
def main():
hook_daten = json.load(sys.stdin)
arbeitsverzeichnis = hook_daten.get('working_directory', '')
# Contracts laden
contracts = lade_projekt_contracts(arbeitsverzeichnis)
if contracts:
# Als Kontext für Claude bereitstellen
kontext = "\n\n".join(contracts)
ergebnis = {
"continue": True,
"message": f"📋 {len(contracts)} Projekt-Contracts geladen:\n{kontext}"
}
else:
ergebnis = {"continue": True}
print(json.dumps(ergebnis))
if __name__ == "__main__":
main()
Wenn ich jetzt in einem Projekt mit .contracts/-Verzeichnis arbeite, werden alle YAML-Dateien darin geladen und Claude als Kontext mitgegeben. Von Anfang an. Ohne dass ich etwas tun muss.
Praxisbeispiel 2: Sitzungsprotokollierung
Für meine Projektarbeit ist es Gold wert zu wissen: Wann habe ich an welchem Projekt gearbeitet? Wie oft? Wie lange? SessionStart ist der perfekte Ort, um das zu erfassen.
Python: Sitzungen in Datenbank protokollieren
#!/usr/bin/env python3
"""SessionStart Hook: Protokolliert Sitzungsbeginn"""
import json
import sys
import mysql.connector
def protokolliere_sitzung(hook_daten):
"""Speichert Sitzungsstart in Audit-Tabelle"""
verbindung = mysql.connector.connect(
host='localhost',
database='projekt_audit',
user='audit_user',
password='sicheres_passwort'
)
cursor = verbindung.cursor()
cursor.execute("""
INSERT INTO sitzungen
(sitzung_id, projekt_pfad, benutzer, gestartet_um)
VALUES (%s, %s, %s, %s)
""", (
hook_daten.get('session_id'),
hook_daten.get('working_directory'),
hook_daten.get('user'),
hook_daten.get('timestamp')
))
verbindung.commit()
verbindung.close()
def main():
hook_daten = json.load(sys.stdin)
try:
protokolliere_sitzung(hook_daten)
print(json.dumps({"continue": True}))
except Exception as e:
# Bei Fehlern trotzdem weitermachen
print(json.dumps({
"continue": True,
"message": f"⚠️ Protokollierung fehlgeschlagen: {e}"
}))
if __name__ == "__main__":
main()
Mit dieser Protokollierung kann ich später auswerten:
- An welchen Projekten habe ich diese Woche gearbeitet?
- Wie oft wurde ein bestimmtes Projekt geöffnet?
- Zu welchen Uhrzeiten arbeite ich am häufigsten?
Das klingt nach Spielerei, aber für die Projektabrechnung oder Zeiterfassung ist es unbezahlbar.
Praxisbeispiel 3: Infrastruktur-Check
Bevor ich mit der Arbeit beginne, will ich wissen: Sind alle Systeme bereit? Läuft die Datenbank? Ist die API erreichbar? Gibt es offene Probleme?
Bash: Infrastruktur-Status prüfen
#!/bin/bash
# SessionStart Hook: Prüft Infrastruktur-Status
STATUS=""
# Datenbank prüfen
if mysql -u root -e "SELECT 1" &>/dev/null; then
STATUS+="✅ MySQL läuft\n"
else
STATUS+="❌ MySQL nicht erreichbar!\n"
fi
# Redis prüfen
if redis-cli ping &>/dev/null; then
STATUS+="✅ Redis läuft\n"
else
STATUS+="⚠️ Redis nicht erreichbar\n"
fi
# Git-Status prüfen
if [ -d .git ]; then
BRANCH=$(git branch --show-current 2>/dev/null)
UNCOMMITTED=$(git status --porcelain 2>/dev/null | wc -l)
STATUS+="📌 Git Branch: $BRANCH ($UNCOMMITTED ungespeicherte Änderungen)\n"
fi
# Ergebnis ausgeben
if [ -n "$STATUS" ]; then
echo "{\"continue\": true, \"message\": \"🔧 Infrastruktur-Status:\n$STATUS\"}"
else
echo '{"continue": true}'
fi
Jetzt weiß ich gleich am Anfang: Alles grün? Oder muss ich erst ein Problem lösen, bevor die eigentliche Arbeit beginnen kann?
Die Kunst des schnellen Starts
Ein wichtiger Punkt, den ich durch Erfahrung gelernt habe: SessionStart-Hooks müssen schnell sein. Wirklich schnell.
Warum? Weil sie den Sitzungsstart verzögern. Und es gibt wenig Frustrierenderes als ein System, das lange braucht, um loszulegen. Du gibst claude ein, drückst Enter, und dann... wartest Du.
Performance-Richtlinien für SessionStart
| Dauer | Bewertung | Empfehlung |
|---|---|---|
| < 100ms | Ideal | Perfekt, nicht merkbar |
| 100-500ms | Akzeptabel | Kurze Verzögerung, vertretbar |
| 500ms-2s | Grenzwertig | Nur wenn wirklich nötig |
| > 2s | Problematisch | Optimieren oder auslagern |
Was mache ich, wenn ein Hook länger braucht? Auslagern. Statt beim Start eine komplexe Analyse durchzuführen, speichere ich die Sitzungs-ID und lasse die Analyse im Hintergrund laufen. Die Ergebnisse kann ich dann bei Bedarf abrufen.
Der Kontext für die ganze Sitzung
Das Besondere an SessionStart: Was ich hier als message zurückgebe, steht Claude für die gesamte Sitzung zur Verfügung. Es ist wie ein Briefing am Morgen: „Das sind die Regeln heute, das ist der Status, das solltest Du wissen."
Python: Umfassenden Kontext bereitstellen
def erstelle_sitzungskontext(arbeitsverzeichnis):
"""Baut einen umfassenden Kontext für die Sitzung"""
kontext_teile = []
# Projekt-Typ erkennen
if (Path(arbeitsverzeichnis) / 'composer.json').exists():
kontext_teile.append("📦 PHP-Projekt (Composer)")
kontext_teile.append("Nutze PSR-4 Autoloading")
kontext_teile.append("Typisiere alle Methoden-Parameter")
elif (Path(arbeitsverzeichnis) / 'package.json').exists():
kontext_teile.append("📦 Node.js-Projekt")
kontext_teile.append("Nutze ES Modules (import/export)")
kontext_teile.append("Typisiere mit TypeScript wenn vorhanden")
# Offene Tickets laden
tickets = lade_offene_tickets(arbeitsverzeichnis)
if tickets:
kontext_teile.append(f"🎫 {len(tickets)} offene Tickets")
for ticket in tickets[:3]:
kontext_teile.append(f" - {ticket}")
return "\n".join(kontext_teile)
Mit diesem Ansatz weiß Claude von Anfang an:
- In welcher Art von Projekt wir arbeiten
- Welche Coding-Standards gelten
- Was gerade ansteht
Das spart enorm viele Erklärungen und führt zu besseren, konsistenteren Ergebnissen.
Was ich nicht in SessionStart mache
Genauso wichtig wie zu wissen, was SessionStart kann, ist zu verstehen, was dort nicht hingehört:
- Keine schweren Berechnungen: Alles, was länger als ein paar hundert Millisekunden dauert, gehört woanders hin.
- Keine interaktiven Abfragen: SessionStart kann nicht auf Benutzereingaben warten. Das ist kein Dialog, das ist ein Startsignal.
- Keine kritischen Operationen: Wenn der Hook fehlschlägt, blockiert er den Sitzungsstart. Nichts Wichtiges hier riskieren.
- Keine Tool-Aufrufe: Bei SessionStart sind die Tools noch nicht verfügbar. Ich kann lesen, aber nicht schreiben.
Das Briefing-Prinzip
Denk an SessionStart wie an ein kurzes Briefing vor einem Meeting. Du sagst nicht alles, was Du weißt. Du sagst das Wichtigste, das Relevanteste, das, was für diese Sitzung gilt. Kurz, prägnant, auf den Punkt.
Zusammenspiel mit anderen Events
SessionStart ist der Anfang einer Kette. Es definiert den Rahmen, in dem alle anderen Events arbeiten. Die Beziehung sieht so aus:
| Event | Beziehung zu SessionStart |
|---|---|
| UserPromptSubmit | Nutzt den Kontext, den SessionStart bereitgestellt hat |
| PreToolUse | Kann auf SessionStart-Konfiguration zugreifen |
| PostToolUse | Protokolliert mit der Session-ID von SessionStart |
| SessionEnd | Schließt, was SessionStart begonnen hat |
SessionStart und SessionEnd bilden zusammen die Klammer um eine Arbeitssitzung. Der Startschuss und der Feierabend. Alles dazwischen passiert im Kontext, den SessionStart gesetzt hat.
Zusammenfassung
SessionStart ist der Moment, in dem alles beginnt. Der Startschuss. Der erste Atemzug einer neuen Arbeitssitzung. Hier bereite ich den Boden:
- Kontext laden: Projektspezifische Regeln, Coding-Standards, aktuelle Aufgaben
- Status prüfen: Läuft die Infrastruktur? Gibt es Probleme?
- Protokollieren: Wann, wo, wer? Für spätere Auswertung
- Rahmen setzen: Was gilt für diese Sitzung?
Dabei gilt: Schnell bleiben. Der Benutzer will arbeiten, nicht warten. Alles, was länger dauert, gehört in den Hintergrund oder in einen späteren Hook.
SessionStart ist kein Ort für komplexe Logik. Es ist ein Ort für klares, schnelles Setup. Der Sprinter im Startblock. Der Schuss fällt. Los geht's.
Der erste Eindruck zählt
SessionStart ist das Erste, was passiert. Wenn es schiefgeht, startet die Sitzung nicht. Wenn es langsam ist, nervt es. Wenn es gut gemacht ist, bemerkt es niemand. Und genau das ist das Ziel: unsichtbar gut arbeiten.
UserPromptSubmit: Die Eingabe abfangen
Kennst Du das Gefühl, wenn Du einen Brief einwirfst und ihn im letzten Moment noch einmal herausziehen könntest? Genau das ist UserPromptSubmit: Der Moment zwischen Absenden und Verarbeiten, in dem ich die Eingabe noch abfangen, prüfen und anreichern kann.
Es ist wie bei einem aufmerksamen Assistenten, der direkt neben dem Briefkasten steht. Du wirfst den Brief ein, aber bevor er in den Schlund fällt, fängt der Assistent ihn ab. Er schaut kurz drauf: „Ist der Empfänger korrekt? Fehlt vielleicht noch etwas? Soll ich einen Vermerk hinzufügen?" Und erst dann geht der Brief weiter auf seine Reise.
Wann wird UserPromptSubmit ausgelöst?
UserPromptSubmit feuert jedes Mal, wenn Du als Benutzer eine Nachricht an Claude sendest. Der Ablauf sieht so aus:
- Du tippst Deine Nachricht
- Du drückst Enter
- UserPromptSubmit feuert
- Alle UserPromptSubmit-Hooks werden ausgeführt
- Claude erhält die (möglicherweise angereicherte) Nachricht
- Claude beginnt mit der Verarbeitung
Das ist der entscheidende Moment zwischen „Benutzer hat etwas gesagt" und „Claude denkt darüber nach". Hier kann ich eingreifen.
Der Unterschied zu SessionStart
SessionStart feuert einmal am Anfang. UserPromptSubmit feuert bei jeder einzelnen Nachricht. Das sind unterschiedliche Zeitpunkte für unterschiedliche Zwecke. SessionStart für die Grundkonfiguration, UserPromptSubmit für die laufende Anreicherung.
Was bekomme ich im Hook zu sehen?
Das Herzstück von UserPromptSubmit ist der Prompt selbst. Ich sehe genau, was der Benutzer geschrieben hat, und kann darauf reagieren.
JSON: UserPromptSubmit Datenstruktur
{
"event": "UserPromptSubmit",
"session_id": "abc123-def456-ghi789...",
"prompt": "Erstelle eine neue PHP-Klasse für die Benutzerauthentifizierung",
"timestamp": "2026-01-13T10:15:00Z"
}
Ich sehe also:
- prompt: Die vollständige Nachricht des Benutzers. Das ist der Inhalt, den ich analysieren und anreichern kann.
- session_id: Die Sitzungs-ID, falls ich Kontext aus SessionStart brauche.
- timestamp: Wann genau wurde die Nachricht gesendet?
Mit dem prompt kann ich erkennen, worum es geht. Will der Benutzer Code schreiben? Eine Frage stellen? Etwas debuggen? Je nach Inhalt kann ich unterschiedlich reagieren.
Die Blocking-Fähigkeit
UserPromptSubmit ist eines der wenigen Events, die blockieren können. Das bedeutet: Ich kann die Verarbeitung komplett stoppen, bevor sie überhaupt beginnt.
Das klingt drastisch, und das ist es auch. Aber manchmal ist es nötig. Wenn jemand versucht, gefährliche Befehle auszuführen oder gegen Projektregeln zu verstoßen, kann ich eingreifen, bevor Schaden entsteht.
Python: Eingabe blockieren
# So blockiere ich eine Eingabe:
ergebnis = {
"hookSpecificOutput": {
"hookEventName": "UserPromptSubmit",
"permissionDecision": "deny",
"permissionDecisionReason": "Diese Aktion ist nicht erlaubt."
}
}
print(json.dumps(ergebnis))
Wenn ich "permissionDecision": "deny" zurückgebe, wird die Nachricht nicht an Claude weitergeleitet. Der Benutzer sieht stattdessen die permissionDecisionReason.
Praxisbeispiel 1: Intelligente Kontext-Anreicherung
Mein häufigster Anwendungsfall: Den Prompt mit relevantem Kontext anreichern. Wenn der Benutzer etwas über eine bestimmte Komponente fragt, lade ich automatisch die Dokumentation zu dieser Komponente.
Python: Prompt mit Kontext anreichern
#!/usr/bin/env python3
"""UserPromptSubmit Hook: Reichert Prompts mit Kontext an"""
import json
import sys
import re
def finde_erwaehnte_komponenten(prompt):
"""Erkennt erwähnte Komponenten im Prompt"""
komponenten = []
# Bekannte Komponenten-Muster
muster = {
r'auth': 'AuthService',
r'user|benutzer': 'UserService',
r'payment|zahlung': 'PaymentService',
r'mail|email': 'MailService',
}
for pattern, komponente in muster.items():
if re.search(pattern, prompt.lower()):
komponenten.append(komponente)
return komponenten
def lade_dokumentation(komponenten):
"""Lädt Dokumentation für die gefundenen Komponenten"""
doku_teile = []
for komponente in komponenten:
doku_pfad = Path(f'.docs/{komponente}.md')
if doku_pfad.exists():
doku_teile.append(f"## {komponente}\n{doku_pfad.read_text()}")
return "\n\n".join(doku_teile)
def main():
hook_daten = json.load(sys.stdin)
prompt = hook_daten.get('prompt', '')
# Erwähnte Komponenten finden
komponenten = finde_erwaehnte_komponenten(prompt)
if komponenten:
# Dokumentation laden und als Kontext hinzufügen
doku = lade_dokumentation(komponenten)
ergebnis = {
"continue": True,
"message": f"📚 Relevante Dokumentation:\n{doku}"
}
else:
ergebnis = {"continue": True}
print(json.dumps(ergebnis))
if __name__ == "__main__":
main()
Wenn der Benutzer jetzt fragt „Wie funktioniert die Authentifizierung?", erkennt mein Hook das Wort „auth" und lädt automatisch die AuthService-Dokumentation. Claude hat dann sofort den nötigen Kontext, ohne dass der Benutzer extra erklären muss, wo die Doku liegt.
Praxisbeispiel 2: Prompt-Protokollierung
Für manche Projekte ist es wichtig nachzuvollziehen, welche Anfragen gestellt wurden. Nicht aus Kontrollzwang, sondern um zu verstehen: Was brauchen die Benutzer? Welche Fragen kommen häufig? Wo fehlt Dokumentation?
Python: Prompts in Datenbank protokollieren
#!/usr/bin/env python3
"""UserPromptSubmit Hook: Protokolliert alle Prompts"""
import json
import sys
import hashlib
import mysql.connector
def protokolliere_prompt(hook_daten):
"""Speichert Prompt in Audit-Tabelle"""
prompt = hook_daten.get('prompt', '')
# Für Datenschutz: Prompt hashen statt speichern
prompt_hash = hashlib.sha256(prompt.encode()).hexdigest()[:16]
# Kategorie erkennen
kategorie = erkenne_kategorie(prompt)
verbindung = mysql.connector.connect(
host='localhost',
database='projekt_audit',
user='audit_user',
password='sicheres_passwort'
)
cursor = verbindung.cursor()
cursor.execute("""
INSERT INTO prompt_log
(sitzung_id, prompt_hash, kategorie, laenge, zeitstempel)
VALUES (%s, %s, %s, %s, %s)
""", (
hook_daten.get('session_id'),
prompt_hash,
kategorie,
len(prompt),
hook_daten.get('timestamp')
))
verbindung.commit()
verbindung.close()
def erkenne_kategorie(prompt):
"""Kategorisiert den Prompt"""
prompt_lower = prompt.lower()
if any(w in prompt_lower for w in ['erstelle', 'schreibe', 'implementiere']):
return 'erstellung'
elif any(w in prompt_lower for w in ['fix', 'fehler', 'bug', 'repariere']):
return 'debugging'
elif any(w in prompt_lower for w in ['erkläre', 'was ist', 'wie funktioniert']):
return 'frage'
else:
return 'sonstiges'
def main():
hook_daten = json.load(sys.stdin)
try:
protokolliere_prompt(hook_daten)
except Exception:
pass # Protokollierung darf nie blockieren
print(json.dumps({"continue": True}))
if __name__ == "__main__":
main()
Mit dieser Protokollierung kann ich später analysieren:
- Wie viele Prompts pro Kategorie (Erstellung, Debugging, Fragen)?
- Wie lang sind die Prompts durchschnittlich?
- Zu welchen Zeiten wird am meisten gefragt?
Das hilft mir, die Dokumentation zu verbessern und häufige Probleme proaktiv anzugehen.
Praxisbeispiel 3: Gefährliche Anfragen blockieren
Manchmal muss ich eingreifen. Wenn jemand versucht, etwas Gefährliches zu tun, oder wenn die Anfrage gegen Projektregeln verstößt, blockiere ich sie, bevor sie überhaupt bei Claude ankommt.
Python: Gefährliche Anfragen blockieren
#!/usr/bin/env python3
"""UserPromptSubmit Hook: Blockiert gefährliche Anfragen"""
import json
import sys
import re
# Gefährliche Muster
VERBOTENE_MUSTER = [
(r'rm\s+-rf\s+/', "Rekursives Löschen von / ist verboten"),
(r'drop\s+database', "Datenbank-Löschung ist verboten"),
(r'truncate\s+table', "Tabellen-Truncate ist verboten"),
(r'chmod\s+777', "chmod 777 ist ein Sicherheitsrisiko"),
(r'--no-verify', "Git-Hooks dürfen nicht übersprungen werden"),
]
def pruefe_prompt(prompt):
"""Prüft Prompt auf verbotene Muster"""
prompt_lower = prompt.lower()
for muster, grund in VERBOTENE_MUSTER:
if re.search(muster, prompt_lower):
return grund
return None
def main():
hook_daten = json.load(sys.stdin)
prompt = hook_daten.get('prompt', '')
# Auf gefährliche Muster prüfen
blockier_grund = pruefe_prompt(prompt)
if blockier_grund:
ergebnis = {
"hookSpecificOutput": {
"hookEventName": "UserPromptSubmit",
"permissionDecision": "deny",
"permissionDecisionReason": f"⛔ Blockiert: {blockier_grund}"
}
}
else:
ergebnis = {"continue": True}
print(json.dumps(ergebnis))
if __name__ == "__main__":
main()
Dieser Hook fängt potentiell gefährliche Anfragen ab, bevor Claude sie überhaupt sieht. Das ist eine zusätzliche Sicherheitsschicht, die unabhängig von Claudes eigenem Sicherheitsbewusstsein funktioniert.
Die Kunst der Anreicherung
Das Mächtigste an UserPromptSubmit ist die Möglichkeit, den Kontext anzureichern. Nicht den Prompt selbst verändern, aber Informationen hinzufügen, die Claude helfen, bessere Antworten zu geben.
Was kann ich als Kontext hinzufügen?
| Kontext-Typ | Beispiel | Nutzen |
|---|---|---|
| Dokumentation | API-Referenz, Style-Guides | Claude kennt die Regeln |
| Code-Kontext | Relevante Klassen, Interfaces | Claude versteht die Architektur |
| Projektstatus | Offene Tickets, Sprint-Ziele | Claude kennt die Prioritäten |
| Historische Daten | Frühere Entscheidungen | Claude wiederholt keine Fehler |
Je mehr relevanten Kontext Claude hat, desto bessere Antworten bekomme ich. UserPromptSubmit ist der ideale Ort, um diesen Kontext automatisch bereitzustellen.
Der Unterschied zwischen Blockieren und Anreichern
Es gibt zwei grundsätzlich verschiedene Reaktionen in UserPromptSubmit:
| Aspekt | Blockieren | Anreichern |
|---|---|---|
| Wann | Bei Verstößen, Gefahren | Bei jeder Anfrage |
| Wie | permissionDecision: deny |
message: "Kontext..." |
| Ergebnis | Anfrage wird abgelehnt | Anfrage wird bereichert |
| Häufigkeit | Selten (Ausnahme) | Häufig (Standard) |
In der Praxis blockiere ich selten. Die meiste Zeit reichere ich an. Blockieren ist die Notbremse, Anreichern ist der Normalfall.
Die goldene Regel
Blockiere nur, wenn es wirklich nötig ist. Ansonsten: Bereichere. Gib Claude mehr Kontext, mehr Informationen, mehr Hilfestellung. Das führt zu besseren Ergebnissen als restriktives Blockieren.
Performance-Überlegungen
Anders als SessionStart (einmal pro Sitzung) feuert UserPromptSubmit bei jeder einzelnen Nachricht. Das bedeutet: Meine Hooks müssen schnell sein. Wirklich schnell.
- Keine schweren Datenbankabfragen: Wenn ich Daten brauche, cache ich sie bei SessionStart.
- Keine externen API-Aufrufe: Zu langsam, zu unzuverlässig. Nur lokale Operationen.
- Einfache Regex statt komplexer NLP: Für die meisten Anwendungsfälle reicht Pattern-Matching.
Ein guter UserPromptSubmit-Hook sollte in unter 50 Millisekunden fertig sein. Alles darüber verzögert jede einzelne Interaktion spürbar.
Zusammenspiel mit PreToolUse
UserPromptSubmit und PreToolUse ergänzen sich. Sie arbeiten auf verschiedenen Ebenen:
| Event | Prüft | Beispiel |
|---|---|---|
| UserPromptSubmit | Die Absicht | „Lösche die Datenbank" → Blockiert |
| PreToolUse | Die Aktion | Bash: rm -rf / → Blockiert |
UserPromptSubmit fängt die Absicht ab. PreToolUse fängt die konkrete Aktion ab. Zusammen bilden sie ein zweistufiges Sicherheitsnetz.
Zusammenfassung
UserPromptSubmit ist der Moment zwischen Absenden und Verarbeiten. Der aufmerksame Assistent am Briefkasten, der jeden Brief noch einmal kurz prüft. Hier kann ich:
- Anreichern: Relevanten Kontext hinzufügen, Dokumentation laden, aktuelle Informationen bereitstellen
- Protokollieren: Anfragen erfassen, Kategorien erkennen, Muster analysieren
- Blockieren: Gefährliche oder unerwünschte Anfragen abfangen, bevor sie verarbeitet werden
Dabei gilt: Schnell bleiben. UserPromptSubmit feuert bei jeder Nachricht. Jede Verzögerung hier spürt der Benutzer bei jeder einzelnen Interaktion.
Das Mächtigste ist die Anreicherung. Je mehr relevanten Kontext Claude hat, desto bessere Antworten bekomme ich. UserPromptSubmit ist der ideale Ort, um diesen Kontext automatisch und unsichtbar bereitzustellen.
Der unsichtbare Helfer
Der beste UserPromptSubmit-Hook ist einer, den der Benutzer nicht bemerkt. Er arbeitet im Hintergrund, reichert den Kontext an, protokolliert bei Bedarf, und nur im Notfall macht er sich durch eine Blockierung bemerkbar. Unsichtbar gut. Genau wie ein guter Assistent.
PreToolUse: Der Wächter vor der Aktion
Kennst Du den Sicherheitsmann am Eingang, der jeden Besucher kurz mustert, bevor er ihn durchlässt? Genau das ist PreToolUse: Der Wächter, der jede Aktion prüft, bevor sie ausgeführt wird. Und der im Zweifelsfall sagt: „Stopp, hier kommst Du nicht durch."
PreToolUse ist das mächtigste aller Hook Events. Hier kann ich nicht nur beobachten, hier kann ich eingreifen. Jede Dateiänderung, jeder Befehl, jede Aktion muss an diesem Wächter vorbei. Und ich entscheide: Durchlassen oder blockieren.
Wann wird PreToolUse ausgelöst?
PreToolUse feuert jedes Mal, bevor Claude ein Werkzeug verwendet. Der Ablauf sieht so aus:
- Claude entscheidet, ein Werkzeug zu nutzen
- Claude bereitet die Parameter vor
- PreToolUse feuert
- Alle PreToolUse-Hooks werden ausgeführt
- Wenn alle Hooks OK geben: Werkzeug wird ausgeführt
- Wenn ein Hook blockiert: Werkzeug wird NICHT ausgeführt
Das ist der kritische Moment. Die Entscheidung fällt hier. Danach ist es zu spät.
Die Macht des Wächters
PreToolUse ist der einzige Zeitpunkt, an dem ich eine Werkzeug-Ausführung tatsächlich verhindern kann. Bei PostToolUse ist es bereits passiert. Hier ist der letzte Moment, um „Nein" zu sagen.
Welche Werkzeuge gibt es?
Claude Code nutzt verschiedene Werkzeuge für verschiedene Aufgaben. PreToolUse sieht jedes einzelne davon:
| Werkzeug | Funktion | Typische PreToolUse-Prüfung |
|---|---|---|
| Edit | Datei bearbeiten | Backup erstellen, Syntax prüfen |
| Write | Neue Datei erstellen | Pfad validieren, Überschreiben verhindern |
| Bash | Befehl ausführen | Gefährliche Befehle blockieren |
| Read | Datei lesen | Zugriff auf sensible Dateien prüfen |
| Glob | Dateien suchen | Meist durchlassen |
| Grep | In Dateien suchen | Meist durchlassen |
| Task | Subagent starten | Ressourcen-Limits prüfen |
Für jedes dieser Werkzeuge kann ich entscheiden: Durchlassen, mit Hinweis durchlassen, oder blockieren.
Was bekomme ich im Hook zu sehen?
PreToolUse liefert mir alle Informationen über die geplante Aktion. Ich sehe nicht nur, welches Werkzeug verwendet werden soll, sondern auch alle Parameter.
JSON: PreToolUse Datenstruktur (Edit)
{
"event": "PreToolUse",
"session_id": "abc123-def456-ghi789...",
"tool_name": "Edit",
"tool_input": {
"file_path": "/var/www/projekt/src/Service.php",
"old_string": "function alte_methode()",
"new_string": "function neue_methode()"
},
"timestamp": "2026-01-13T10:30:00Z"
}
Bei einem Bash-Befehl sehe ich den kompletten Befehl:
JSON: PreToolUse Datenstruktur (Bash)
{
"event": "PreToolUse",
"session_id": "abc123-def456-ghi789...",
"tool_name": "Bash",
"tool_input": {
"command": "npm install lodash",
"description": "Install lodash package"
},
"timestamp": "2026-01-13T10:31:00Z"
}
Mit diesen Informationen kann ich präzise entscheiden, ob die Aktion erlaubt ist oder nicht.
Tool-Spezifische Matcher
Eine Besonderheit von PreToolUse: Ich kann Hooks auf bestimmte Werkzeuge beschränken. Das spart Ressourcen und macht den Code übersichtlicher.
JSON: Hook-Konfiguration mit Matcher
{
"name": "file_backup",
"event": "PreToolUse",
"enabled": true,
"matcher": "^(Edit|Write)$",
"command": "python3 /hooks/backup_before_edit.py"
}
Dieser Hook feuert nur bei Edit und Write, nicht bei Read, Bash oder anderen Werkzeugen. Der Matcher ist ein regulärer Ausdruck, der auf den Werkzeugnamen angewendet wird.
Praxisbeispiel 1: Automatisches Datei-Backup
Mein wichtigster PreToolUse-Hook: Vor jeder Dateiänderung ein Backup erstellen. Das gibt mir die Sicherheit, jederzeit zurückgehen zu können.
Python: Automatisches Backup vor Änderungen
#!/usr/bin/env python3
"""PreToolUse Hook: Erstellt Backup vor Dateiänderungen"""
import json
import sys
import shutil
from pathlib import Path
from datetime import datetime
BACKUP_VERZEICHNIS = Path('/var/backups/claude-code')
def erstelle_backup(dateipfad):
"""Kopiert die Datei ins Backup-Verzeichnis"""
datei = Path(dateipfad)
if not datei.exists():
return None # Neue Datei, kein Backup nötig
# Backup-Pfad mit Zeitstempel
zeitstempel = datetime.now().strftime('%Y%m%d_%H%M%S')
backup_name = f"{datei.stem}_{zeitstempel}{datei.suffix}"
backup_pfad = BACKUP_VERZEICHNIS / backup_name
# Verzeichnis erstellen falls nötig
BACKUP_VERZEICHNIS.mkdir(parents=True, exist_ok=True)
# Kopieren
shutil.copy2(datei, backup_pfad)
return backup_pfad
def main():
hook_daten = json.load(sys.stdin)
werkzeug = hook_daten.get('tool_name')
eingabe = hook_daten.get('tool_input', {})
# Nur bei Dateiänderungen
if werkzeug not in ['Edit', 'Write']:
print(json.dumps({"continue": True}))
return
dateipfad = eingabe.get('file_path', '')
try:
backup_pfad = erstelle_backup(dateipfad)
if backup_pfad:
print(json.dumps({
"continue": True,
"message": f"💾 Backup erstellt: {backup_pfad}"
}))
else:
print(json.dumps({"continue": True}))
except Exception as e:
# Bei Backup-Fehlern trotzdem weitermachen, aber warnen
print(json.dumps({
"continue": True,
"message": f"⚠️ Backup fehlgeschlagen: {e}"
}))
if __name__ == "__main__":
main()
Mit diesem Hook habe ich für jede geänderte Datei eine Sicherungskopie. Wenn etwas schiefgeht, kann ich jederzeit zurück.
Praxisbeispiel 2: Gefährliche Befehle blockieren
Das zweite Standbein meiner Sicherheitsstrategie: Bestimmte Befehle komplett verbieten. Es gibt Dinge, die sollen einfach nicht passieren.
Python: Gefährliche Befehle blockieren
#!/usr/bin/env python3
"""PreToolUse Hook: Blockiert gefährliche Bash-Befehle"""
import json
import sys
import re
# Verbotene Muster mit Erklärungen
VERBOTENE_MUSTER = [
(r'\brm\s+-rf\s+/', "Rekursives Löschen von / ist verboten"),
(r'\bcurl\b', "curl ist verboten. Nutze: http get URL"),
(r'\bwget\b', "wget ist verboten. Nutze: download URL"),
(r'\bmysql\s+-e', "Direkter MySQL-Zugriff verboten. Nutze: db_sql"),
(r'--no-verify', "Git-Hooks dürfen nicht übersprungen werden"),
(r'--force\s+push', "Force-Push ist verboten"),
(r'\bchmod\s+777\b', "chmod 777 ist ein Sicherheitsrisiko"),
]
# Erlaubte Ausnahmen
ERLAUBTE_MUSTER = [
r'curl\s+--version', # Versionsabfrage OK
]
def pruefe_befehl(befehl):
"""Prüft Befehl auf verbotene Muster"""
# Erst Ausnahmen prüfen
for erlaubt in ERLAUBTE_MUSTER:
if re.search(erlaubt, befehl, re.IGNORECASE):
return None
# Dann verbotene Muster prüfen
for muster, grund in VERBOTENE_MUSTER:
if re.search(muster, befehl, re.IGNORECASE):
return grund
return None
def main():
hook_daten = json.load(sys.stdin)
# Nur bei Bash-Befehlen prüfen
if hook_daten.get('tool_name') != 'Bash':
print(json.dumps({"continue": True}))
return
befehl = hook_daten.get('tool_input', {}).get('command', '')
blockier_grund = pruefe_befehl(befehl)
if blockier_grund:
ergebnis = {
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "deny",
"permissionDecisionReason": f"⛔ {blockier_grund}"
}
}
else:
ergebnis = {"continue": True}
print(json.dumps(ergebnis))
if __name__ == "__main__":
main()
Dieser Hook macht mehrere Dinge:
- Er blockiert gefährliche Befehle wie
rm -rf / - Er erzwingt die Nutzung meiner sicheren CLI-Tools statt
curlundwget - Er verhindert riskante Git-Operationen
- Er erlaubt explizite Ausnahmen (wie Versionsabfragen)
Praxisbeispiel 3: Dokumentation vor Bearbeitung anzeigen
Ein eleganter Anwendungsfall: Bevor Claude eine Datei bearbeitet, zeige ich ihm die relevante Dokumentation. So kennt er die Regeln, bevor er anfängt.
Python: Dokumentation vor Bearbeitung laden
#!/usr/bin/env python3
"""PreToolUse Hook: Zeigt relevante Dokumentation vor Bearbeitung"""
import json
import sys
from pathlib import Path
def finde_dokumentation(dateipfad):
"""Sucht Dokumentation für die Datei oder ihr Verzeichnis"""
datei = Path(dateipfad)
doku_teile = []
# Projekt-weite CLAUDE.md
projekt_root = datei.parent
while projekt_root != projekt_root.parent:
claude_md = projekt_root / 'CLAUDE.md'
if claude_md.exists():
doku_teile.append(f"📋 Projekt-Regeln:\n{claude_md.read_text()[:500]}...")
break
projekt_root = projekt_root.parent
# Verzeichnis-spezifische Dokumentation
verzeichnis_doku = datei.parent / 'README.md'
if verzeichnis_doku.exists():
doku_teile.append(f"📁 Verzeichnis-Info:\n{verzeichnis_doku.read_text()[:300]}...")
# Datei-spezifischer Contract
contract = datei.with_suffix('.contract.yaml')
if contract.exists():
doku_teile.append(f"📄 Datei-Contract:\n{contract.read_text()}")
return "\n\n".join(doku_teile) if doku_teile else None
def main():
hook_daten = json.load(sys.stdin)
# Nur bei Datei-Bearbeitung
if hook_daten.get('tool_name') not in ['Edit', 'Write']:
print(json.dumps({"continue": True}))
return
dateipfad = hook_daten.get('tool_input', {}).get('file_path', '')
doku = finde_dokumentation(dateipfad)
if doku:
print(json.dumps({
"continue": True,
"message": f"📚 Relevante Dokumentation:\n{doku}"
}))
else:
print(json.dumps({"continue": True}))
if __name__ == "__main__":
main()
Mit diesem Hook weiß Claude automatisch, welche Regeln für diese Datei gelten. Er muss nicht erst nachfragen, und ich muss nicht alles bei jedem Prompt wiederholen.
Die drei Reaktionsmöglichkeiten
In PreToolUse habe ich drei grundlegende Möglichkeiten zu reagieren:
Was kann PreToolUse tun?
| Reaktion | Wie | Wann nutzen |
|---|---|---|
| Durchlassen | {"continue": true} |
Aktion ist OK |
| Mit Hinweis durchlassen | {"continue": true, "message": "..."} |
OK, aber Claude sollte etwas beachten |
| Blockieren | {"hookSpecificOutput": {...}} |
Aktion ist verboten |
In der Praxis blockiere ich selten. Die meisten Hooks lassen durch, manchmal mit einem Hinweis. Blockieren ist die Ausnahme, nicht die Regel.
Performance ist kritisch
PreToolUse feuert bei jeder einzelnen Werkzeug-Nutzung. Das können viele Ereignisse in kurzer Zeit sein. Wenn Claude zehn Dateien liest, feuert PreToolUse zehn Mal. Meine Hooks müssen schnell sein.
- Keine schweren Operationen: Backup-Kopieren ja, Komprimieren nein.
- Früh abbrechen:
Wenn der Hook für dieses Werkzeug nicht relevant ist, sofort
{"continue": true}zurückgeben. - Matcher nutzen: Statt im Hook zu prüfen, ob das Werkzeug relevant ist, lieber einen Matcher in der Konfiguration verwenden.
Die 100-Millisekunden-Regel
Ein guter PreToolUse-Hook sollte in unter 100 Millisekunden fertig sein. Alles darüber verlangsamt jede einzelne Werkzeug-Nutzung spürbar. Bei einer Session mit hunderten von Tool-Aufrufen summiert sich das schnell.
Das Zusammenspiel der Wächter
PreToolUse arbeitet nicht allein. Es ist Teil eines mehrschichtigen Sicherheitssystems:
| Schicht | Event | Prüft |
|---|---|---|
| 1 | UserPromptSubmit | Die Absicht des Benutzers |
| 2 | PreToolUse | Die konkrete Aktion |
| 3 | PostToolUse | Das Ergebnis der Aktion |
Wenn UserPromptSubmit die erste Sicherheitskontrolle ist (Absichten prüfen), dann ist PreToolUse die zweite (Aktionen prüfen), und PostToolUse die dritte (Ergebnisse prüfen). Zusammen bilden sie ein robustes Sicherheitsnetz.
Typische Hook-Kombinationen
In meinem Setup nutze ich mehrere PreToolUse-Hooks gleichzeitig. Jeder hat seine spezifische Aufgabe:
JSON: Mehrere PreToolUse-Hooks
[
{
"name": "backup_vor_aenderung",
"event": "PreToolUse",
"matcher": "^(Edit|Write)$",
"command": "python3 /hooks/backup.py"
},
{
"name": "gefaehrliche_befehle",
"event": "PreToolUse",
"matcher": "^Bash$",
"command": "python3 /hooks/bash_guard.py"
},
{
"name": "dokumentation_laden",
"event": "PreToolUse",
"matcher": "^(Edit|Write)$",
"command": "python3 /hooks/doku_awareness.py"
}
]
Die Hooks werden der Reihe nach ausgeführt. Wenn einer blockiert, werden die nachfolgenden nicht mehr aufgerufen.
Zusammenfassung
PreToolUse ist der Wächter vor der Aktion. Der letzte Moment, um „Nein" zu sagen. Hier kann ich:
- Prüfen: Ist diese Aktion erlaubt? Entspricht sie den Regeln?
- Vorbereiten: Backup erstellen, Kontext laden, Dokumentation bereitstellen
- Blockieren: Gefährliche oder verbotene Aktionen komplett verhindern
- Hinweisen: Claude relevante Informationen mitgeben, bevor er handelt
Die Macht von PreToolUse liegt in der Kombination: Ich kann die Aktion sehen, bevor sie passiert, und ich kann sie stoppen. Das ist einzigartig unter allen Hook Events.
Dabei gilt: Mit Macht kommt Verantwortung. Ein fehlerhafter PreToolUse-Hook kann die gesamte Arbeit blockieren. Ein langsamer Hook verlangsamt jede Aktion. Deshalb: Gründlich testen, schnell halten, und nur blockieren, wenn es wirklich nötig ist.
Der gute Wächter
Ein guter Wächter lässt die meisten Besucher durch. Er grüßt freundlich, manchmal gibt er einen Hinweis, und nur selten muss er „Stopp" sagen. So sollte auch PreToolUse sein: Unterstützend im Normalfall, streng nur wenn nötig. Der beste Hook ist einer, dessen Blockierungen man nie sieht, weil sie nie nötig werden.
PostToolUse: Die Nachbereitung
Kennst Du das Gefühl, wenn Du gerade etwas erledigt hast und dann noch einmal drüberschaust, ob auch wirklich alles passt? Genau das ist PostToolUse: Der Moment direkt nach der Aktion, in dem ich prüfen, protokollieren und bei Bedarf Feedback geben kann.
Wenn PreToolUse der Türsteher ist, dann ist PostToolUse der Qualitätsprüfer am Ende des Fließbands. Die Aktion ist bereits passiert, das Werkzeug hat seine Arbeit getan. Jetzt geht es darum, das Ergebnis zu begutachten.
Wann wird PostToolUse ausgelöst?
PostToolUse feuert unmittelbar nach Abschluss einer Werkzeug-Ausführung. Das bedeutet:
- Die Datei wurde bereits bearbeitet
- Der Befehl wurde bereits ausgeführt
- Die Änderung ist bereits geschehen
Das ist ein wichtiger Unterschied zu PreToolUse: Dort konnte ich noch eingreifen und sagen „Stopp, das machen wir nicht!" Bei PostToolUse ist es dafür zu spät. Die Aktion ist passiert. Aber ich kann reagieren, dokumentieren und Feedback geben.
Der richtige Zeitpunkt
PostToolUse ist wie der Moment, in dem der Koch das Gericht fertig angerichtet hat und der Küchenchef einen letzten Blick darauf wirft. Er kann das Gericht nicht mehr „unkochen", aber er kann prüfen, ob alles stimmt, bevor es zum Gast geht. Und er kann sich Notizen machen für das nächste Mal.
Was bekomme ich im Hook zu sehen?
Das Schöne an PostToolUse: Ich bekomme nicht nur die Eingabe, sondern auch das Ergebnis. Das ist wie ein vollständiges Protokoll der gerade abgeschlossenen Aktion.
JSON: PostToolUse Datenstruktur
{
"event": "PostToolUse",
"session_id": "abc123...",
"tool_name": "Edit",
"tool_input": {
"file_path": "/var/www/projekt/src/Service.php",
"old_string": "function alte_methode()",
"new_string": "function neue_methode()"
},
"tool_result": {
"success": true,
"message": "File updated successfully"
},
"timestamp": "2026-01-13T12:10:05Z"
}
Ich sehe also:
- tool_name: Welches Werkzeug wurde verwendet?
- tool_input: Was war die Eingabe? (Bei Edit: welche Datei, was wurde ersetzt)
- tool_result: Was kam dabei heraus? (Erfolg oder Misserfolg, Fehlermeldungen)
Mit diesen Informationen kann ich eine Menge anfangen.
Warum nicht einfach blockieren?
Du fragst Dich vielleicht: „Warum kann ich bei PostToolUse nicht auch blockieren wie bei PreToolUse?"
Die Antwort ist einfach: Die Aktion ist bereits passiert. Die Datei wurde geändert, der Befehl wurde ausgeführt. Es gibt nichts mehr zu blockieren. Das wäre, als würdest Du nach dem Versenden einer E-Mail sagen: „Eigentlich wollte ich die gar nicht abschicken." Zu spät.
Aber: Du kannst Feedback geben. Du kannst sagen: „Achtung, da ist etwas schiefgelaufen!" Und Claude wird dieses Feedback bei der nächsten Aktion berücksichtigen.
Praxisbeispiel 1: Automatische Syntaxprüfung
Mein häufigstes Einsatzgebiet für PostToolUse: Syntaxprüfung direkt nach einer Dateiänderung. Das funktioniert so:
- Claude bearbeitet eine PHP-Datei
- PostToolUse feuert
- Mein Hook führt
php -l dateiname.phpaus - Bei einem Syntaxfehler gebe ich eine Warnung zurück
- Claude sieht die Warnung und korrigiert den Fehler
Das ist wie ein aufmerksamer Korrekturleser, der nach jedem Absatz kurz drüberschaut.
Python: Syntax-Prüfung nach Bearbeitung
#!/usr/bin/env python3
"""PostToolUse Hook: Prüft Code-Syntax nach Änderungen"""
import json
import sys
import subprocess
def pruefe_php_syntax(dateipfad):
"""Führt php -l aus und gibt Fehler zurück"""
ergebnis = subprocess.run(
['php', '-l', dateipfad],
capture_output=True, text=True
)
if ergebnis.returncode != 0:
return ergebnis.stderr
return None
def main():
hook_daten = json.load(sys.stdin)
# Nur bei Dateiänderungen reagieren
if hook_daten.get('tool_name') not in ['Edit', 'Write']:
print(json.dumps({"continue": True}))
return
dateipfad = hook_daten.get('tool_input', {}).get('file_path', '')
# PHP-Dateien prüfen
if dateipfad.endswith('.php'):
fehler = pruefe_php_syntax(dateipfad)
if fehler:
print(json.dumps({
"continue": True,
"message": f"⚠️ PHP-Syntaxfehler gefunden:\n{fehler}"
}))
return
print(json.dumps({"continue": True}))
if __name__ == "__main__":
main()
Der Schlüssel ist die message im Rückgabewert. Diese Nachricht wird Claude angezeigt und fließt in den Kontext ein. Claude sieht dann: „Aha, da ist ein Syntaxfehler. Den sollte ich beheben."
Praxisbeispiel 2: Automatische Berechtigungskorrektur
Ein Problem, das mich früher regelmäßig erwischt hat: Claude erstellt eine Datei, aber der Webserver kann sie nicht lesen, weil die Berechtigungen falsch sind. Jetzt löse ich das automatisch:
Bash: Berechtigungen automatisch korrigieren
#!/bin/bash
# PostToolUse Hook: Setzt Datei-Berechtigungen
# Hook-Daten einlesen
HOOK_DATEN=$(cat)
WERKZEUG=$(echo "$HOOK_DATEN" | jq -r '.tool_name')
DATEIPFAD=$(echo "$HOOK_DATEN" | jq -r '.tool_input.file_path // empty')
# Nur bei Dateioperationen
if [[ "$WERKZEUG" == "Edit" || "$WERKZEUG" == "Write" ]]; then
if [[ -n "$DATEIPFAD" && -f "$DATEIPFAD" ]]; then
# Besitzer auf www-data setzen
chown www-data:www-data "$DATEIPFAD"
# Lesbare Berechtigungen setzen
chmod 644 "$DATEIPFAD"
fi
fi
# Immer weitermachen
echo '{"continue": true}'
Dieser Hook arbeitet still im Hintergrund. Er gibt keine Nachricht aus, er erledigt einfach seinen Job. Jede Datei, die Claude erstellt oder bearbeitet, bekommt automatisch die richtigen Berechtigungen.
Praxisbeispiel 3: Änderungen protokollieren
Für größere Projekte ist es Gold wert, wenn ich nachvollziehen kann, was wann geändert wurde. PostToolUse ist der perfekte Ort dafür:
Python: Änderungen in Datenbank protokollieren
def protokolliere_aenderung(hook_daten):
"""Schreibt jede Änderung in die Audit-Tabelle"""
werkzeug = hook_daten.get('tool_name')
eingabe = hook_daten.get('tool_input', {})
ergebnis = hook_daten.get('tool_result', {})
sitzung = hook_daten.get('session_id')
zeitstempel = hook_daten.get('timestamp')
# In Datenbank schreiben
cursor.execute("""
INSERT INTO audit_log
(sitzung_id, werkzeug, eingabe, ergebnis, zeitstempel)
VALUES (%s, %s, %s, %s, %s)
""", (
sitzung,
werkzeug,
json.dumps(eingabe),
json.dumps(ergebnis),
zeitstempel
))
connection.commit()
Mit diesem Protokoll kann ich später nachvollziehen:
- Welche Dateien wurden in dieser Sitzung geändert?
- Gab es Fehler bei bestimmten Operationen?
- Wie lange dauerten die einzelnen Aktionen?
Der Feedback-Mechanismus
Das Mächtigste an PostToolUse ist der Feedback-Mechanismus. Wenn ich eine Nachricht zurückgebe, sieht Claude diese Nachricht und kann darauf reagieren.
So funktioniert der Kreislauf
| Schritt | Was passiert | Beispiel |
|---|---|---|
| 1 | Claude führt Aktion aus | Bearbeitet eine PHP-Datei |
| 2 | PostToolUse feuert | Hook prüft Syntax |
| 3 | Hook findet Problem | Fehlende Klammer entdeckt |
| 4 | Hook gibt Feedback | „Syntaxfehler in Zeile 42" |
| 5 | Claude sieht Feedback | Erkennt das Problem |
| 6 | Claude korrigiert | Fügt fehlende Klammer ein |
Dieser Kreislauf entsteht ganz natürlich. Ich muss Claude nicht explizit sagen: „Wenn Du diesen Fehler siehst, dann tu das." Claude versteht den Kontext und reagiert entsprechend.
Selbstkorrigierender Workflow
Durch den Feedback-Mechanismus entsteht ein selbstkorrigierender Workflow. Fehler werden nicht erst am Ende einer langen Arbeitssitzung entdeckt, sondern sofort nach jeder einzelnen Änderung. Das spart Zeit, Nerven und vor allem: Token.
Was ich damit alles machen kann
Die Möglichkeiten sind vielfältig. Hier eine Sammlung von Ideen, die ich selbst nutze oder die mir begegnet sind:
- Code-Qualität prüfen: Syntax-Checks, Linter, statische Analyse direkt nach jeder Änderung.
- Berechtigungen setzen: Automatisch die richtigen Datei-Besitzer und Zugriffsrechte vergeben.
- Knowledge-Base aktualisieren: Geänderte Dateien in eine Vektor-Datenbank für semantische Suche indizieren.
- Tests anstoßen: Automatisch Unit-Tests für geänderte Module laufen lassen.
- Dokumentation erinnern: Claude darauf hinweisen, wenn Code geändert wurde, aber die Dokumentation nicht.
- Metriken erfassen: Zyklomatische Komplexität, Zeilenanzahl, TODO-Kommentare zählen und bei Überschreitung warnen.
- Changelog führen: Automatisch einen Changelog-Eintrag für jede signifikante Änderung erstellen.
- Backups erstellen: Vor der Änderung war die Datei so, jetzt ist sie anders. Beide Versionen archivieren.
Ein wichtiger Unterschied zu PreToolUse
Lass mich das noch einmal deutlich machen, weil es für das Verständnis wichtig ist:
| Aspekt | PreToolUse | PostToolUse |
|---|---|---|
| Zeitpunkt | VOR der Aktion | NACH der Aktion |
| Kann blockieren? | Ja | Nein |
| Hat Ergebnis? | Nein (noch nicht ausgeführt) | Ja (Erfolg/Misserfolg) |
| Typischer Einsatz | Validierung, Prävention | Prüfung, Protokollierung, Feedback |
| Analogie | Türsteher | Qualitätsprüfer |
Beide Ereignisse ergänzen sich wunderbar. PreToolUse verhindert Fehler, PostToolUse entdeckt sie. Zusammen bilden sie ein robustes Sicherheitsnetz.
Der doppelte Boden
In meinem Setup nutze ich beide Ereignisse: PreToolUse prüft, ob die geplante Aktion sinnvoll ist. PostToolUse prüft, ob das Ergebnis korrekt ist. So entsteht ein doppelter Boden, der die meisten Probleme abfängt, bevor sie größeren Schaden anrichten können.
Zusammenfassung
PostToolUse ist der Moment der Reflexion. Die Aktion ist geschehen, jetzt ist Zeit für:
- Prüfen: War das Ergebnis korrekt? Gibt es Syntaxfehler?
- Protokollieren: Was wurde geändert? Wann? Von wem?
- Reagieren: Berechtigungen setzen, Backups erstellen, Tests anstoßen
- Feedback geben: Claude auf Probleme hinweisen, damit sie korrigiert werden können
Anders als PreToolUse kann PostToolUse nicht blockieren. Aber das macht nichts. Denn die wahre Stärke liegt im Feedback-Mechanismus: Eine Warnung hier, ein Hinweis dort, und Claude korrigiert sich selbst. Das ist eleganter als jedes Blockieren.
Stop: Der Abbruch
Manchmal muss man die Notbremse ziehen. Das Stop-Ereignis feuert genau dann: Wenn Du Escape drückst, Ctrl+C tippst oder auf andere Weise sagst: „Halt, stopp, nicht weiter!" Es ist der Moment, in dem alles zum Stillstand kommt.
Das ist nicht unbedingt etwas Schlechtes. Ein Abbruch kann viele Gründe haben: Du hast gemerkt, dass Du den falschen Auftrag gegeben hast. Die Operation dauert zu lange. Du brauchst die Rechenleistung für etwas anderes. Oder Du hast einfach Deine Meinung geändert.
Wann wird Stop ausgelöst?
Das Stop-Ereignis feuert, wenn der Benutzer eine laufende Claude-Operation aktiv unterbricht. Das kann zu verschiedenen Zeitpunkten passieren:
- Mitten in einer Dateiänderung
- Während einer langen Code-Analyse
- Bei der Ausführung eines Konsolenbefehls
- Während Claude gerade nachdenkt
Der Zeitpunkt ist unvorhersehbar. Genau das macht den Stop-Hook so wichtig: Ich muss darauf vorbereitet sein, dass jederzeit der Stecker gezogen werden kann.
Die Notbremse im Zug
Ein Stop-Ereignis ist wie die Notbremse im Zug. Sie kann jederzeit gezogen werden, und dann muss alles geordnet zum Stillstand kommen. Keine Panik, kein Chaos, sondern ein kontrolliertes Anhalten. Der Zug bleibt nicht einfach stehen, er bremst sicher ab.
Was bekomme ich zu sehen?
Das Stop-Ereignis liefert mir wichtige Informationen darüber, was gerade passiert ist:
JSON: Stop Datenstruktur
{
"event": "Stop",
"session_id": "abc123...",
"reason": "user_interrupt",
"timestamp": "2026-01-13T12:15:00Z",
"last_tool": "Edit",
"last_tool_status": "in_progress"
}
Besonders interessant sind hier:
- reason: Warum wurde gestoppt? (z.B. user_interrupt)
- last_tool: Welches Werkzeug war gerade aktiv?
- last_tool_status: War es noch mitten in der Ausführung?
Mit diesen Informationen kann ich entscheiden, was aufgeräumt werden muss.
Warum ist das wichtig?
Ein Abbruch kann zu einem ungültigen Zustand führen. Beispiel: Claude war gerade dabei, eine Funktion umzubenennen. Die alte Referenz wurde gelöscht, aber die neue noch nicht erstellt. Jetzt ist der Code kaputt.
Oder: Ein langer Batch-Prozess wurde unterbrochen. 47 von 100 Dateien wurden verarbeitet. Welche 47? Wo wurde aufgehört?
Der Stop-Hook gibt mir die Chance, solche Situationen zu behandeln.
Praxisbeispiel 1: Offene Tasks markieren
Wenn ich ein Task-System verwende, möchte ich wissen, welche Aufgaben unterbrochen wurden:
Python: Tasks als unterbrochen markieren
#!/usr/bin/env python3
"""Stop Hook: Markiert unterbrochene Tasks"""
import json
import sys
from datetime import datetime
def markiere_unterbrochene_tasks(sitzung_id):
"""Findet und markiert alle laufenden Tasks dieser Sitzung"""
cursor.execute("""
UPDATE tasks
SET status = 'unterbrochen',
unterbrochen_am = %s,
notiz = 'Abbruch durch Benutzer'
WHERE sitzung_id = %s
AND status = 'in_bearbeitung'
""", (datetime.now(), sitzung_id))
return cursor.rowcount
def main():
hook_daten = json.load(sys.stdin)
sitzung_id = hook_daten.get('session_id')
try:
anzahl = markiere_unterbrochene_tasks(sitzung_id)
if anzahl > 0:
print(json.dumps({
"continue": True,
"message": f"⏹️ {anzahl} Task(s) als unterbrochen markiert"
}))
else:
print(json.dumps({"continue": True}))
except Exception:
# Bei Stop-Hooks: Niemals fehlschlagen!
print(json.dumps({"continue": True}))
if __name__ == "__main__":
main()
Der wichtige Teil hier: Der try/except Block um alles herum. Ein Stop-Hook darf niemals fehlschlagen. Der Benutzer hat „Stopp!" gesagt und erwartet, dass das System anhält. Nicht, dass es mit einer Fehlermeldung crasht.
Praxisbeispiel 2: Arbeitsstand sichern
Bei längeren Operationen kann es sinnvoll sein, den Fortschritt zu sichern:
Python: Fortschritt speichern
def sichere_fortschritt(sitzung_id):
"""Speichert den aktuellen Arbeitsstand"""
fortschritt = {
'sitzung_id': sitzung_id,
'zeitpunkt': datetime.now().isoformat(),
'bearbeitete_dateien': hole_bearbeitete_dateien(sitzung_id),
'offene_aufgaben': hole_offene_aufgaben(sitzung_id),
'letzter_schritt': hole_letzten_schritt(sitzung_id)
}
# In Datei speichern für späteren Neustart
dateiname = f"/tmp/claude_fortschritt_{sitzung_id}.json"
with open(dateiname, 'w') as f:
json.dump(fortschritt, f, indent=2)
return dateiname
So kann ich beim nächsten Start nachschauen: „Ah, hier wurde unterbrochen. Diese Dateien wurden bereits bearbeitet, diese noch nicht."
Praxisbeispiel 3: Temporäre Dateien aufräumen
Manche Operationen erstellen Zwischendateien. Die sollten nicht herumliegen bleiben:
Bash: Temporäre Dateien löschen
#!/bin/bash
# Stop Hook: Räumt temporäre Dateien auf
HOOK_DATEN=$(cat)
SITZUNG_ID=$(echo "$HOOK_DATEN" | jq -r '.session_id')
# Temporäre Dateien dieser Sitzung finden und löschen
TEMP_PATTERN="/tmp/claude_*_${SITZUNG_ID}*"
if ls $TEMP_PATTERN &>/dev/null; then
ANZAHL=$(ls $TEMP_PATTERN 2>/dev/null | wc -l)
rm -f $TEMP_PATTERN 2>/dev/null
echo "{\"continue\": true, \"message\": \"🧹 $ANZAHL temporäre Datei(en) aufgeräumt\"}"
else
echo '{"continue": true}'
fi
Die goldene Regel für Stop-Hooks
Es gibt eine Regel, die wichtiger ist als alle anderen:
Schnell und fehlerfrei
Ein Stop-Hook muss schnell sein und darf niemals fehlschlagen. Der Benutzer hat abgebrochen. Er will nicht warten. Er will schon gar nicht eine Fehlermeldung sehen. Fange alle Exceptions ab. Setze Timeouts. Beende immer sauber mit {"continue": true}.
Warum ist das so wichtig? Weil der Benutzer bereits ungeduldig ist. Er hat „Stopp" gesagt, weil etwas nicht stimmt oder weil er keine Zeit mehr hat. Das Letzte, was er jetzt braucht, ist ein Hook, der ewig läuft oder mit einem Fehler abbricht.
Was kann ich damit alles machen?
Hier eine Sammlung von Ideen:
- Tasks markieren: Laufende Aufgaben als „unterbrochen" kennzeichnen, damit beim nächsten Start klar ist, wo weitergemacht werden muss.
- Fortschritt speichern: Den aktuellen Stand in einer Datei oder Datenbank sichern für späteren Neustart.
- Temporäres aufräumen: Zwischendateien, Cache-Einträge und andere temporäre Daten löschen.
- Locks freigeben: Falls Dateien oder Ressourcen gesperrt wurden, diese wieder freigeben.
- Benachrichtigen: Bei wichtigen Prozessen eine Nachricht senden, dass der Vorgang unterbrochen wurde.
- Zusammenfassung erstellen: Kurz dokumentieren, was bereits erledigt wurde und was noch offen ist.
Der Unterschied zu SessionEnd
Du fragst Dich vielleicht: „Was ist der Unterschied zwischen Stop und SessionEnd?"
| Aspekt | Stop | SessionEnd |
|---|---|---|
| Auslöser | Benutzer unterbricht aktiv | Sitzung endet normal |
| Zeitpunkt | Mitten in einer Operation | Nach Abschluss aller Operationen |
| Erwartung | Unvollständiger Zustand möglich | Alles sollte abgeschlossen sein |
| Dringlichkeit | Sehr hoch (schnell sein!) | Normal |
Stop ist der Notfall. SessionEnd ist der planmäßige Abschluss. Beide sind wichtig, aber sie erfordern unterschiedliche Reaktionen.
Zusammenfassung
Das Stop-Ereignis ist Deine Chance, bei einem Abbruch aufzuräumen. Markiere unterbrochene Tasks, sichere den Fortschritt, räume temporäre Dateien auf. Aber denk dran: Schnell sein und niemals fehlschlagen. Der Benutzer wartet.
Du kannst Dir die Aufzeichnung hier ansehen:
Als Mitglied der KI-Gemeinschaft kannst Du Dir die vollständige Aufzeichnung ansehen und auf alle weiteren Hook-Events zugreifen: SubagentStop, SessionEnd, Notification, PermissionRequest und PreCompact.
php if (!isset($_SESSION['authenticated']) || $_SESSION['authenticated'] !== true): ? oder [Anmelden](<?= AuthHelper::LOGIN_URL ?>?return=<?= urlencode($_SERVER['REQUEST_URI'] ?? '/claude-code-hooks-events') ?>) php endif; ?
SubagentStop: Der Sub-Task ist fertig
Kennst Du das Gefühl, wenn Du mehrere Leute losgeschickt hast, um verschiedene Dinge zu erledigen, und nach und nach kommen sie zurück mit ihren Ergebnissen? Genau das ist SubagentStop: Der Moment, in dem ein Helfer meldet: „Chef, ich bin fertig!"
Claude kann nämlich nicht nur selbst arbeiten, sondern auch kleine Helfer losschicken. Diese Helfer heißen „Subagents" und sind spezialisierte Mini-Claudes, die bestimmte Aufgaben besonders gut können.
Was sind Subagents überhaupt?
Ein Subagent ist wie ein Spezialist, den Du für eine bestimmte Aufgabe engagierst. Claude kann über das Task-Werkzeug verschiedene Typen starten:
Die Spezialisten im Team
| Subagent-Typ | Spezialgebiet | Typischer Einsatz |
|---|---|---|
| Explore | Codebase durchsuchen | „Finde alle Dateien die X enthalten" |
| Plan | Implementierung planen | „Erstelle einen Plan für Feature Y" |
| Bash | Terminal-Operationen | „Führe diese Befehle aus" |
| General-Purpose | Komplexe Aufgaben | „Recherchiere und analysiere Z" |
Das Schöne daran: Diese Subagents können parallel arbeiten. Während der eine die Codebase durchsucht, plant der andere schon die Implementierung. Das spart Zeit.
Wann wird SubagentStop ausgelöst?
SubagentStop feuert, sobald ein Subagent seine Arbeit beendet. Das kann verschiedene Gründe haben:
- Der Subagent hat seine Aufgabe erfolgreich erledigt
- Der Subagent ist auf einen Fehler gestoßen
- Der Subagent wurde abgebrochen
- Das Zeitlimit wurde erreicht
In jedem Fall bekomme ich eine Benachrichtigung: „Der Helfer ist zurück."
Der Bote kehrt zurück
Ein Subagent ist wie ein Bote, den Du in eine andere Stadt schickst. Du weißt nicht genau, wann er zurückkommt, aber Du weißt, dass er sich meldet, sobald er da ist. SubagentStop ist diese Meldung: „Ich bin zurück, hier ist, was ich gefunden habe."
Was bekomme ich zu sehen?
SubagentStop liefert mir alle wichtigen Informationen über den abgeschlossenen Helfer:
JSON: SubagentStop Datenstruktur
{
"event": "SubagentStop",
"session_id": "abc123...",
"subagent_id": "sub_xyz789",
"subagent_type": "Explore",
"status": "completed",
"result_summary": "Found 15 matching files",
"duration_ms": 4500,
"timestamp": "2026-01-13T12:20:00Z"
}
Besonders interessant:
- subagent_type: Welcher Spezialist war das?
- status: Erfolgreich oder fehlgeschlagen?
- result_summary: Was hat er gefunden/erledigt?
- duration_ms: Wie lange hat es gedauert?
Praxisbeispiel 1: Performance-Monitoring
Wenn ich mehrere Subagents einsetze, will ich wissen, wie schnell sie sind. Vielleicht ist ein bestimmter Typ immer langsam? Das kann ich tracken:
Python: Subagent-Laufzeiten erfassen
#!/usr/bin/env python3
"""SubagentStop Hook: Erfasst Performance-Metriken"""
import json
import sys
def erfasse_metrik(hook_daten):
"""Speichert Laufzeit und Status in Metriken-Tabelle"""
cursor.execute("""
INSERT INTO subagent_metriken
(sitzung_id, subagent_id, typ, status, dauer_ms, zeitpunkt)
VALUES (%s, %s, %s, %s, %s, %s)
""", (
hook_daten.get('session_id'),
hook_daten.get('subagent_id'),
hook_daten.get('subagent_type'),
hook_daten.get('status'),
hook_daten.get('duration_ms'),
hook_daten.get('timestamp')
))
connection.commit()
def main():
hook_daten = json.load(sys.stdin)
erfasse_metrik(hook_daten)
# Warnung bei langsamen Subagents
dauer = hook_daten.get('duration_ms', 0)
typ = hook_daten.get('subagent_type', 'Unknown')
if dauer > 30000: # Mehr als 30 Sekunden
print(json.dumps({
"continue": True,
"message": f"⏱️ {typ}-Agent brauchte {dauer/1000:.1f}s"
}))
else:
print(json.dumps({"continue": True}))
if __name__ == "__main__":
main()
Mit der Zeit sehe ich dann Muster: „Aha, der Explore-Agent ist immer schnell, aber der Plan-Agent braucht oft länger." Das hilft mir, die Arbeit besser zu planen.
Praxisbeispiel 2: Ergebnisse zusammenführen
Wenn mehrere Subagents parallel arbeiten, möchte ich ihre Ergebnisse am Ende zusammenführen:
Python: Ergebnisse aggregieren
def aggregiere_ergebnisse(sitzung_id, subagent_id, ergebnis):
"""Sammelt Ergebnisse aller Subagents einer Sitzung"""
# Ergebnis speichern
ergebnisse = lade_bisherige_ergebnisse(sitzung_id)
ergebnisse[subagent_id] = ergebnis
speichere_ergebnisse(sitzung_id, ergebnisse)
# Prüfen ob alle Subagents fertig sind
erwartete = hole_erwartete_subagents(sitzung_id)
fertige = set(ergebnisse.keys())
if erwartete == fertige:
# Alle fertig! Zusammenfassung erstellen
zusammenfassung = erstelle_zusammenfassung(ergebnisse)
return f"✅ Alle {len(fertige)} Subagents fertig:\n{zusammenfassung}"
return None
So weiß ich genau, wann alle Helfer zurück sind und kann dann den nächsten Schritt einleiten.
Praxisbeispiel 3: Fehlerbehandlung
Nicht jeder Subagent ist erfolgreich. Manchmal gibt es Fehler. Die will ich mitbekommen:
Python: Fehlgeschlagene Subagents behandeln
def main():
hook_daten = json.load(sys.stdin)
status = hook_daten.get('status')
typ = hook_daten.get('subagent_type')
subagent_id = hook_daten.get('subagent_id')
if status != 'completed':
# Fehler protokollieren
protokolliere_fehler(hook_daten)
# Entscheiden: Retry oder aufgeben?
versuche = hole_bisherige_versuche(subagent_id)
if versuche < 3:
nachricht = f"⚠️ {typ}-Agent fehlgeschlagen (Versuch {versuche}/3)"
else:
nachricht = f"❌ {typ}-Agent endgültig fehlgeschlagen"
print(json.dumps({
"continue": True,
"message": nachricht
}))
else:
print(json.dumps({"continue": True}))
Warum ist das wichtig?
Subagents sind mächtig, aber sie sind auch Blackboxen. Ich schicke sie los und hoffe, dass sie zurückkommen. SubagentStop gibt mir die Kontrolle zurück:
- Ich weiß, wann ein Helfer fertig ist
- Ich weiß, ob er erfolgreich war
- Ich weiß, wie lange er gebraucht hat
- Ich kann reagieren, wenn etwas schiefgeht
Orchestrierung
Mit SubagentStop kann ich komplexe Workflows orchestrieren: Mehrere Subagents parallel starten, auf ihre Ergebnisse warten, diese zusammenführen und dann den nächsten Schritt einleiten. Das ist wie ein Dirigent, der sein Orchester koordiniert.
Was kann ich damit alles machen?
- Performance messen: Laufzeiten tracken, Engpässe identifizieren, langsame Subagents optimieren.
- Ergebnisse sammeln: Parallele Ergebnisse aggregieren und konsolidierte Reports erstellen.
- Fehler behandeln: Bei Fehlern automatisch Retries starten oder alternative Strategien einleiten.
- Ressourcen aufräumen: Temporäre Dateien löschen, die der Subagent angelegt hat.
- Benachrichtigen: Bei lang laufenden Tasks eine Nachricht senden, wenn sie fertig sind.
- Workflows steuern: Den nächsten Schritt erst starten, wenn alle Vorarbeiten erledigt sind.
Zusammenfassung
SubagentStop ist der Moment, in dem ein Helfer zurückkommt. Nutze ihn, um Ergebnisse zu sammeln, Performance zu messen und Fehler zu behandeln. Je mehr Subagents Du einsetzt, desto wertvoller wird dieser Hook.
SessionEnd: Der Abschluss
Jeder Tag hat ein Ende, jede Arbeitssitzung ebenfalls. SessionEnd ist der Moment, in dem die Lichter ausgehen und ich aufräumen kann, bevor ich gehe. Es ist der planmäßige, geordnete Abschluss einer Claude-Code-Sitzung.
Anders als beim Stop-Ereignis, das plötzlich und unerwartet kommt, ist SessionEnd vorhersehbar. Die Arbeit ist getan, der Benutzer hat „Tschüss" gesagt, und jetzt ist Zeit für die letzten Handgriffe.
Wann wird SessionEnd ausgelöst?
SessionEnd feuert, wenn eine Claude-Code-Sitzung regulär beendet wird. Das kann verschiedene Ursachen haben:
- Das Terminal wird geschlossen
- Der Benutzer gibt einen Exit-Befehl
- Die Sitzung erreicht ihr Timeout
- Das Gespräch endet natürlich
In all diesen Fällen habe ich einen Moment Zeit, um sauber abzuschließen.
Feierabend
SessionEnd ist wie der Feierabend nach einem Arbeitstag. Du räumst Deinen Schreibtisch auf, speicherst Deine Dateien, fährst den Computer herunter und schaltest das Licht aus. Alles in Ruhe, ohne Hektik.
Was bekomme ich zu sehen?
SessionEnd liefert mir eine Zusammenfassung der gesamten Sitzung:
JSON: SessionEnd Datenstruktur
{
"event": "SessionEnd",
"session_id": "abc123...",
"duration_seconds": 1847,
"tool_calls": 42,
"prompts": 15,
"timestamp": "2026-01-13T12:30:00Z"
}
Das sind wertvolle Informationen:
- duration_seconds: Wie lange lief die Sitzung? (hier: etwa 30 Minuten)
- tool_calls: Wie viele Werkzeuge wurden aufgerufen? (hier: 42)
- prompts: Wie viele Eingaben gab es? (hier: 15)
Mit diesen Zahlen kann ich interessante Statistiken erstellen.
Praxisbeispiel 1: Sitzungsstatistiken speichern
Ich sammle gerne Statistiken über meine Arbeitssitzungen. Das hilft mir zu verstehen, wie ich Claude nutze und wo ich effizienter werden kann:
Python: Sitzungsstatistiken erfassen
#!/usr/bin/env python3
"""SessionEnd Hook: Speichert Sitzungsstatistiken"""
import json
import sys
from datetime import datetime
def speichere_statistiken(hook_daten):
"""Schreibt Sitzungsstatistiken in die Datenbank"""
cursor.execute("""
INSERT INTO sitzungs_statistiken
(sitzung_id, dauer_sekunden, werkzeug_aufrufe, eingaben, beendet_am)
VALUES (%s, %s, %s, %s, %s)
""", (
hook_daten.get('session_id'),
hook_daten.get('duration_seconds'),
hook_daten.get('tool_calls'),
hook_daten.get('prompts'),
datetime.now()
))
connection.commit()
def main():
hook_daten = json.load(sys.stdin)
speichere_statistiken(hook_daten)
# Kurze Zusammenfassung ausgeben
dauer = hook_daten.get('duration_seconds', 0)
minuten = dauer // 60
werkzeuge = hook_daten.get('tool_calls', 0)
print(json.dumps({
"continue": True,
"message": f"📊 Sitzung: {minuten} Min, {werkzeuge} Werkzeuge"
}))
if __name__ == "__main__":
main()
Nach einigen Wochen kann ich dann auswerten: Wie lange sind meine typischen Sitzungen? Welche Tage sind besonders produktiv? Wie viele Werkzeuge nutze ich durchschnittlich?
Praxisbeispiel 2: Aufräumen
Während einer Sitzung entstehen oft temporäre Dateien, Cache-Einträge und andere Überbleibsel. SessionEnd ist der perfekte Moment zum Aufräumen:
Bash: Temporäre Dateien aufräumen
#!/bin/bash
# SessionEnd Hook: Räumt auf
HOOK_DATEN=$(cat)
SITZUNG_ID=$(echo "$HOOK_DATEN" | jq -r '.session_id')
# Temporäre Dateien dieser Sitzung
rm -f /tmp/claude_*_${SITZUNG_ID}* 2>/dev/null
# Alte Cache-Dateien (älter als 7 Tage)
find /tmp -name "claude_cache_*" -mtime +7 -delete 2>/dev/null
# Leere Verzeichnisse aufräumen
find /tmp -type d -empty -name "claude_*" -delete 2>/dev/null
echo '{"continue": true}'
Praxisbeispiel 3: Abschluss-Report
Manchmal möchte ich am Ende einer Sitzung wissen, was eigentlich alles passiert ist. Ein automatischer Report kann da helfen:
Python: Sitzungs-Report erstellen
def erstelle_report(sitzung_id):
"""Erstellt eine Zusammenfassung der Sitzung"""
# Bearbeitete Dateien abrufen
dateien = hole_bearbeitete_dateien(sitzung_id)
# Tasks abrufen
tasks = hole_abgeschlossene_tasks(sitzung_id)
# Fehler abrufen
fehler = hole_aufgetretene_fehler(sitzung_id)
report = {
'sitzung_id': sitzung_id,
'bearbeitete_dateien': len(dateien),
'dateien_liste': dateien[:10], # Erste 10
'abgeschlossene_tasks': len(tasks),
'fehler': len(fehler)
}
# Report speichern
speichere_report(report)
return report
Ein wichtiger Hinweis
So schön SessionEnd auch ist, es gibt eine Einschränkung:
Keine Garantie
SessionEnd wird nicht garantiert ausgelöst. Bei einem Absturz, einem Kill-Signal oder einem Stromausfall gibt es kein SessionEnd. Kritische Daten sollten deshalb kontinuierlich gespeichert werden, nicht erst am Ende. SessionEnd ist für „Nice-to-have"-Aufräumarbeiten, nicht für lebensnotwendige Operationen.
Das bedeutet: Wichtige Daten speichere ich laufend (zum Beispiel in PostToolUse). SessionEnd nutze ich für Statistiken, Reports und Aufräumarbeiten, die nicht kritisch sind.
Der Unterschied zu Stop
Zur Erinnerung, weil es wichtig ist:
| Aspekt | Stop | SessionEnd |
|---|---|---|
| Auslöser | Benutzer unterbricht aktiv | Sitzung endet normal |
| Zeitpunkt | Mitten in einer Operation | Nach Abschluss aller Operationen |
| Dringlichkeit | Hoch (schnell sein!) | Normal (Zeit für Statistiken) |
| Analogie | Notbremse | Feierabend |
Was kann ich damit alles machen?
- Statistiken speichern: Dauer, Werkzeugnutzung, Eingaben erfassen für spätere Auswertung.
- Aufräumen: Temporäre Dateien, Caches und andere Überbleibsel löschen.
- Reports erstellen: Zusammenfassung der Sitzung für Dokumentation oder Abrechnung.
- Verbindungen schließen: Datenbankverbindungen, API-Clients und andere Ressourcen sauber beenden.
- Tasks übertragen: Offene Aufgaben in ein externes System exportieren.
- Benachrichtigen: Bei langen Sitzungen eine Nachricht senden, dass die Arbeit beendet ist.
Zusammenfassung
SessionEnd ist der geordnete Abschluss, der Feierabend nach getaner Arbeit. Nutze ihn für Statistiken, Reports und Aufräumarbeiten. Aber vergiss nicht: Er ist nicht garantiert. Kritische Daten gehören in andere Hooks.
Notification: Die Glocke läutet
In jedem System gibt es Momente, in denen etwas Wichtiges passiert und jemand davon erfahren sollte. Notification ist die Glocke, die läutet, wenn es Neuigkeiten gibt. Nicht laut und aufdringlich, aber hörbar genug, um Aufmerksamkeit zu erregen.
In einem mittelalterlichen Dorf hängt auf dem Kirchturm eine Glocke. Sie läutet zur vollen Stunde, bei Gefahr, bei freudigen Ereignissen. Die Dorfbewohner wissen: Wenn die Glocke klingt, ist etwas passiert. Notification ist diese Glocke im Claude-Code-System.
Wann läutet die Glocke?
Das Notification-Ereignis feuert bei verschiedenen System-Ereignissen:
- Ein Hintergrund-Task ist fertig
- Das Kontext-Limit nähert sich
- Eine System-Warnung tritt auf
- Ein Update ist verfügbar
All diese Ereignisse haben etwas gemeinsam: Sie passieren „nebenbei", während ich mit etwas anderem beschäftigt bin. Die Glocke informiert mich, ohne meine aktuelle Arbeit zu unterbrechen.
Der Unterschied zum Postboten
Anders als ein Postbote, der an der Tür klingelt und wartet, bis jemand aufmacht, ist Notification eine Kirchenglocke: Sie läutet, und wer sie hört, kann reagieren. Wer sie nicht hört oder ignoriert, dem wird nichts aufgezwungen. Notification-Hooks können den Ablauf nicht blockieren. Sie sind reine Beobachter.
Was bekomme ich zu hören?
Wenn die Glocke läutet, erfahre ich, was passiert ist:
JSON: Notification Datenstruktur
{
"event": "Notification",
"session_id": "abc123...",
"notification_type": "background_task_complete",
"title": "Task abgeschlossen",
"message": "Hintergrund-Befehl beendet (Exit-Code 0)",
"severity": "info",
"timestamp": "2026-01-13T12:35:00Z"
}
Die wichtigsten Felder im Überblick:
- notification_type: Was für eine Benachrichtigung ist das?
- title: Eine kurze Überschrift
- message: Die eigentliche Nachricht
- severity: Wie wichtig ist das? (info, warning, error)
Die verschiedenen Glockentöne
Nicht jede Glocke klingt gleich. Je nach Anlass gibt es verschiedene Töne:
| Typ | Bedeutung | Analogie |
|---|---|---|
background_task_complete |
Hintergrund-Aufgabe fertig | Feierabendglocke |
context_warning |
Kontext-Limit nähert sich | Warnglocke |
system_warning |
System-Warnung | Alarmglocke |
update_available |
Update verfügbar | Nachrichtenglocke |
Praxisbeispiel 1: Benachrichtigungen weiterleiten
Ich möchte wissen, was in meinen Claude-Code-Sitzungen passiert, auch wenn ich nicht direkt davor sitze. Also leite ich wichtige Benachrichtigungen an meinen Messenger weiter:
Python: Benachrichtigungen an Slack senden
#!/usr/bin/env python3
"""Notification Hook: Leitet wichtige Meldungen an Slack weiter"""
import json
import sys
import requests
# Nur diese Typen weiterleiten
WICHTIGE_TYPEN = ['context_warning', 'system_warning']
def sende_an_slack(titel, nachricht, schwere):
"""Sendet eine Nachricht an Slack"""
# Emoji je nach Schweregrad
emoji = {
'info': ':information_source:',
'warning': ':warning:',
'error': ':rotating_light:'
}.get(schwere, ':bell:')
requests.post(SLACK_WEBHOOK_URL, json={
'text': f"{emoji} *{titel}*\n{nachricht}"
})
def main():
hook_daten = json.load(sys.stdin)
typ = hook_daten.get('notification_type')
if typ in WICHTIGE_TYPEN:
sende_an_slack(
hook_daten.get('title'),
hook_daten.get('message'),
hook_daten.get('severity')
)
print(json.dumps({"continue": True}))
if __name__ == "__main__":
main()
So erfahre ich von Warnungen, egal wo ich gerade bin. Die Glocke läutet, und mein Telefon vibriert.
Praxisbeispiel 2: Kontext-Warnung intelligent behandeln
Wenn das Kontext-Limit näher rückt, möchte ich vorbereitet sein. Ich speichere den aktuellen Stand automatisch:
Python: Bei Kontext-Warnung sichern
def behandle_kontext_warnung(hook_daten):
"""Reagiert auf Kontext-Limit-Warnungen"""
sitzung_id = hook_daten.get('session_id')
# Aktuellen Fortschritt sichern
speichere_sitzungszustand(sitzung_id)
# Offene Tasks exportieren
exportiere_offene_tasks(sitzung_id)
# Arbeitsverzeichnis protokollieren
protokolliere_arbeitsverzeichnis(sitzung_id)
return {
'gesichert': True,
'sitzung': sitzung_id,
'zeitstempel': datetime.now().isoformat()
}
Wenn dann tatsächlich der Kontext voll ist und die Sitzung kompaktiert wird, habe ich bereits alles Wichtige gesichert.
Praxisbeispiel 3: Benachrichtigungs-Statistiken
Über die Zeit sammeln sich viele Benachrichtigungen an. Ein Hook kann sie aggregieren und mir später einen Überblick geben:
Python: Benachrichtigungen zählen
def zaehle_benachrichtigung(hook_daten):
"""Zählt Benachrichtigungen nach Typ und Schweregrad"""
typ = hook_daten.get('notification_type')
schwere = hook_daten.get('severity')
datum = datetime.now().strftime('%Y-%m-%d')
cursor.execute("""
INSERT INTO benachrichtigungs_statistik
(datum, typ, schweregrad, anzahl)
VALUES (%s, %s, %s, 1)
ON DUPLICATE KEY UPDATE anzahl = anzahl + 1
""", (datum, typ, schwere))
connection.commit()
Nach einigen Wochen kann ich auswerten: Wie viele Kontext-Warnungen bekomme ich? Gibt es Muster? Welche Tage sind besonders „laut"?
Die Weisheit der Glocke
Die Glocke lehrt uns etwas Wichtiges: Nicht jede Information muss sofort verarbeitet werden. Manche Dinge sind gut zu wissen, aber nicht dringend. Notification gibt mir die Möglichkeit zu entscheiden:
- Will ich diese Art von Benachrichtigung überhaupt hören?
- Will ich sie weiterleiten oder nur protokollieren?
- Will ich bei bestimmten Typen besondere Aktionen auslösen?
Reine Beobachtung
Anders als andere Hooks kann Notification den Ablauf nicht beeinflussen. Du kannst nicht „blockieren" oder „modifizieren". Du kannst nur beobachten, protokollieren und weiterleiten. Das ist gewollt: Benachrichtigungen sollen informieren, nicht kontrollieren.
Was kann ich damit alles machen?
- Weiterleiten: Sende Benachrichtigungen an Slack, Teams, E-Mail oder beliebige Webhooks.
- Filtern: Ignoriere unwichtige Meldungen und konzentriere Dich auf das Wesentliche.
- Aggregieren: Sammle Statistiken über Benachrichtigungen für spätere Analyse.
- Alarmieren: Bei kritischen Warnungen sofort SMS oder Anruf auslösen.
- Vorbereiten: Bei Kontext-Warnungen automatisch sichern und exportieren.
- Dokumentieren: Alle Benachrichtigungen für spätere Nachvollziehbarkeit protokollieren.
Zusammenfassung
Notification ist die Glocke des Systems. Sie läutet, wenn etwas passiert ist. Du entscheidest, ob Du hinschaust, ob Du es weitersagst, oder ob Du es ignorierst. Die Glocke zwingt nichts auf, sie informiert nur. Nutze sie weise.
PermissionRequest: Der Schlüsselmeister
In jedem System gibt es Bereiche, die nicht für jedermann zugänglich sind. Manche Türen sind verschlossen, und nur wer den richtigen Schlüssel hat, darf eintreten. PermissionRequest ist der Schlüsselmeister, der entscheidet, wer Zugang bekommt.
Denk an ein großes Gebäude: Im Erdgeschoss kann jeder frei herumlaufen. Aber manche Etagen, manche Räume, sind nur mit Schlüsselkarte zugänglich. Der Schlüsselmeister sitzt am Empfang und prüft: „Haben Sie die Berechtigung für diesen Bereich?"
Wann wird der Schlüsselmeister gerufen?
PermissionRequest feuert, wenn Claude eine Aktion ausführen möchte, die besondere Berechtigungen erfordert:
- Ausführung potenziell gefährlicher Befehle
- Zugriff auf sensible Dateien
- Netzwerk-Operationen
- Systemänderungen
Anders als andere Hooks ist PermissionRequest nicht nur ein Beobachter. Er hat echte Macht: Er kann Anfragen genehmigen oder ablehnen.
Macht und Verantwortung
Der Schlüsselmeister trägt eine große Verantwortung. Wenn er zu streng ist, wird die Arbeit behindert. Wenn er zu locker ist, können Schäden entstehen. Die Kunst liegt in der Balance.
Was bekommt der Schlüsselmeister zu sehen?
Bei jeder Anfrage erhält der Hook detaillierte Informationen:
JSON: PermissionRequest Datenstruktur
{
"event": "PermissionRequest",
"session_id": "abc123...",
"permission_type": "bash_execution",
"requested_action": {
"tool": "Bash",
"command": "rm -rf /tmp/build/*"
},
"risk_level": "medium",
"timestamp": "2026-01-13T12:40:00Z"
}
Die wichtigsten Informationen:
- permission_type: Was für eine Berechtigung wird angefragt?
- requested_action: Was genau soll passieren?
- risk_level: Wie riskant ist diese Aktion? (low, medium, high)
Die Entscheidungsmöglichkeiten
Der Schlüsselmeister hat drei Optionen:
| Entscheidung | Bedeutung | Wann sinnvoll? |
|---|---|---|
allow |
Genehmigung erteilen | Bekannte, sichere Operationen |
deny |
Ablehnung | Gefährliche oder verbotene Aktionen |
| (keine Antwort) | Normale Abfrage | Bei Unsicherheit, manuelle Prüfung |
Praxisbeispiel 1: Automatische Genehmigung für sichere Befehle
Manche Befehle sind so harmlos, dass ich sie automatisch genehmigen kann. Das spart Zeit und unterbricht den Arbeitsfluss nicht:
Python: Sichere Befehle automatisch genehmigen
#!/usr/bin/env python3
"""PermissionRequest Hook: Auto-Genehmigung für sichere Befehle"""
import json
import sys
import re
# Diese Muster sind sicher und werden automatisch genehmigt
SICHERE_MUSTER = [
r'^git\s+status', # Git Status
r'^git\s+log', # Git Log
r'^git\s+diff', # Git Diff
r'^ls\s+', # Verzeichnisinhalt
r'^php\s+-l\s+', # PHP Syntax-Check
r'^cat\s+/var/log/', # Log-Dateien lesen
r'^npm\s+test', # Tests ausführen
]
def ist_sicher(befehl):
"""Prüft, ob ein Befehl als sicher gilt"""
for muster in SICHERE_MUSTER:
if re.match(muster, befehl):
return True
return False
def main():
hook_daten = json.load(sys.stdin)
aktion = hook_daten.get('requested_action', {})
befehl = aktion.get('command', '')
if ist_sicher(befehl):
print(json.dumps({
"hookSpecificOutput": {
"permissionDecision": "allow",
"permissionDecisionReason": "Auto: Sicherer Befehl"
}
}))
else:
# Keine automatische Entscheidung, normale Abfrage
print(json.dumps({"continue": True}))
if __name__ == "__main__":
main()
Mit dieser Konfiguration werden harmlose Lese-Operationen sofort durchgewunken, ohne dass ich jedes Mal bestätigen muss.
Praxisbeispiel 2: Gefährliche Befehle blockieren
Es gibt Befehle, die ich unter keinen Umständen automatisch ausführen lassen möchte. Der Schlüsselmeister kann sie kategorisch ablehnen:
Python: Gefährliche Befehle blockieren
# Diese Muster werden IMMER abgelehnt
VERBOTENE_MUSTER = [
r'^rm\s+-rf\s+/', # Rekursives Löschen ab Root
r'^chmod\s+777', # Unsichere Berechtigungen
r'^curl.*\|\s*bash', # Piped Execution
r'^wget.*\|\s*sh', # Piped Execution
r'>\s*/etc/', # Schreiben in /etc
r'^sudo\s+', # Sudo-Befehle
]
def ist_verboten(befehl):
"""Prüft, ob ein Befehl verboten ist"""
for muster in VERBOTENE_MUSTER:
if re.search(muster, befehl):
return True
return False
def main():
hook_daten = json.load(sys.stdin)
aktion = hook_daten.get('requested_action', {})
befehl = aktion.get('command', '')
if ist_verboten(befehl):
print(json.dumps({
"hookSpecificOutput": {
"permissionDecision": "deny",
"permissionDecisionReason": "Blockiert: Gefährlicher Befehl"
}
}))
elif ist_sicher(befehl):
# ... (wie oben)
else:
print(json.dumps({"continue": True}))
Diese Befehle werden niemals automatisch genehmigt. Selbst wenn ich sie manuell bestätigen wollte, würde der Hook sie abfangen.
Praxisbeispiel 3: Kontextabhängige Entscheidungen
Manchmal hängt die Entscheidung vom Kontext ab. Ein Befehl, der in einem Projekt sicher ist, kann in einem anderen gefährlich sein:
Python: Projektspezifische Regeln
import os
# Projektspezifische Regeln
PROJEKT_REGELN = {
'/var/www/dev.karlkratz.de': {
'erlaubt': [r'^npm\s+', r'^php\s+', r'^git\s+'],
'verboten': [r'^rm\s+-rf']
},
'/var/www/produktion': {
'erlaubt': [r'^git\s+status'], # Nur lesende Git-Befehle
'verboten': [r'^git\s+push', r'^rm\s+'] # Kein Push, kein Löschen
}
}
def pruefe_mit_kontext(befehl, arbeitsverzeichnis):
"""Prüft Berechtigung basierend auf Projektkontext"""
regeln = PROJEKT_REGELN.get(arbeitsverzeichnis, {})
# Erst verbotene Muster prüfen
for muster in regeln.get('verboten', []):
if re.match(muster, befehl):
return 'deny'
# Dann erlaubte Muster
for muster in regeln.get('erlaubt', []):
if re.match(muster, befehl):
return 'allow'
return None # Keine automatische Entscheidung
So kann ich in der Entwicklungsumgebung großzügiger sein als in der Produktionsumgebung.
Die goldene Regel des Schlüsselmeisters
Der weise Schlüsselmeister folgt einem Prinzip:
Im Zweifel: Fragen
Automatische Genehmigungen sind bequem, aber gefährlich. Beschränke sie auf wirklich unbedenkliche Operationen. Bei allem anderen: Lass die normale Berechtigungsabfrage zu. Es ist besser, einmal zu viel zu fragen als einmal zu wenig.
Was kann ich damit alles machen?
- Auto-Genehmigung: Sichere, häufige Befehle automatisch durchwinken.
- Auto-Ablehnung: Gefährliche Befehle kategorisch blockieren.
- Protokollierung: Alle Berechtigungsanfragen für Audit-Zwecke speichern.
- Kontextregeln: Unterschiedliche Regeln für verschiedene Projekte.
- Risikobewertung: Bei hohem Risiko zusätzliche Prüfungen durchführen.
- Benachrichtigung: Bei sensiblen Anfragen eine Warnung senden.
Ein Wort zur Vorsicht
Der Schlüsselmeister hat echte Macht. Mit großer Macht kommt große Verantwortung:
- Teste Deine Regeln gründlich, bevor Du sie aktivierst
- Beginne restriktiv und lockere erst bei Bedarf
- Protokolliere alle automatischen Entscheidungen
- Überprüfe regelmäßig, ob Deine Regeln noch sinnvoll sind
Zusammenfassung
PermissionRequest ist der Schlüsselmeister des Systems. Er entscheidet, welche Aktionen genehmigt werden und welche nicht. Nutze diese Macht weise: Automatisiere das Sichere, blockiere das Gefährliche, und frage im Zweifel lieber einmal mehr.
PreCompact: Der letzte Koffer
Eine lange Reise steht an, und Du darfst nur einen Koffer mitnehmen. Der Raum ist begrenzt, Du musst entscheiden: Was ist wirklich wichtig? Was muss unbedingt mit? PreCompact ist der Moment, in dem Du Deinen Koffer packst, bevor die Reise losgeht.
Claude hat ein begrenztes Gedächtnis, das sogenannte Kontextfenster. Wenn dieses voll wird, muss Platz geschaffen werden. Ältere Nachrichten werden zusammengefasst oder vergessen. Dabei können wichtige Details verloren gehen. PreCompact ist Deine letzte Chance, die wichtigen Dinge zu retten.
Wann wird PreCompact ausgelöst?
PreCompact feuert kurz bevor die Komprimierung beginnt. Das passiert, wenn:
- Das Kontextfenster fast voll ist
- Neue Informationen hinzugefügt werden müssen
- Claude beschließt, den alten Kontext zusammenzufassen
In diesem Moment habe ich noch Zugriff auf den vollen Kontext, aber gleich nicht mehr.
Der Koffer-Moment
Es ist wie beim Packen vor einer Reise: Du siehst all Deine Sachen, aber Du weißt, dass nicht alles in den Koffer passt. Jetzt musst Du entscheiden, was wirklich wichtig ist. Was Du nicht einpackst, wirst Du auf der Reise nicht dabei haben.
Was bekomme ich zu sehen?
Der Hook liefert wichtige Informationen über den bevorstehenden Verlust:
JSON: PreCompact Datenstruktur
{
"event": "PreCompact",
"session_id": "abc123...",
"current_context_tokens": 180000,
"max_context_tokens": 200000,
"messages_to_compact": 45,
"timestamp": "2026-01-13T12:45:00Z"
}
Die wichtigsten Informationen:
- current_context_tokens: Wie voll ist das Gedächtnis aktuell?
- max_context_tokens: Wie viel Platz gibt es insgesamt?
- messages_to_compact: Wie viele Nachrichten werden komprimiert?
Im Beispiel sind 180.000 von 200.000 Tokens belegt, und 45 Nachrichten werden gleich zusammengefasst.
Praxisbeispiel 1: Wichtige Entscheidungen sichern
Während einer langen Arbeitssitzung treffe ich viele Entscheidungen. Manche davon sind wichtig für den weiteren Verlauf. Bevor sie vergessen werden, sichere ich sie:
Python: Kritische Informationen sichern
#!/usr/bin/env python3
"""PreCompact Hook: Sichert wichtige Kontext-Informationen"""
import json
import sys
from datetime import datetime
def sichere_kontext(hook_daten):
"""Speichert kritische Informationen vor der Komprimierung"""
sitzung_id = hook_daten.get('session_id')
kontext_sicherung = {
'sitzung_id': sitzung_id,
'zeitstempel': datetime.now().isoformat(),
'tokens_vor_komprimierung': hook_daten.get('current_context_tokens'),
'komprimierte_nachrichten': hook_daten.get('messages_to_compact')
}
# In Datei speichern
dateiname = f"/tmp/kontext_sicherung_{sitzung_id}.json"
with open(dateiname, 'w') as f:
json.dump(kontext_sicherung, f, indent=2)
return dateiname
def main():
hook_daten = json.load(sys.stdin)
dateiname = sichere_kontext(hook_daten)
print(json.dumps({
"continue": True,
"message": f"Kontext gesichert: {dateiname}"
}))
if __name__ == "__main__":
main()
Diese Sicherung kann ich später nutzen, um den Kontext wiederherzustellen oder nachzuvollziehen, was vor der Komprimierung passiert ist.
Praxisbeispiel 2: Offene Tasks exportieren
Wenn der Kontext komprimiert wird, könnten offene Aufgaben vergessen werden. Um das zu verhindern, exportiere ich sie in eine externe Datei:
Python: Offene Tasks sichern
def exportiere_offene_tasks(sitzung_id):
"""Exportiert alle offenen Tasks vor der Komprimierung"""
# Offene Tasks aus der Datenbank holen
cursor.execute("""
SELECT inhalt, status, erstellt_am
FROM sitzungs_tasks
WHERE sitzung_id = %s AND status != 'completed'
ORDER BY erstellt_am
""", (sitzung_id,))
offene_tasks = cursor.fetchall()
if offene_tasks:
export = {
'sitzung_id': sitzung_id,
'exportiert_am': datetime.now().isoformat(),
'grund': 'PreCompact - Kontext-Komprimierung',
'offene_tasks': [
{'inhalt': t[0], 'status': t[1], 'erstellt': t[2]}
for t in offene_tasks
]
}
speichere_task_export(export)
return len(offene_tasks)
return 0
Nach der Komprimierung weiß ich dann immer noch, was zu tun ist.
Praxisbeispiel 3: Eigene Zusammenfassung erstellen
Claude erstellt bei der Komprimierung eine automatische Zusammenfassung. Aber manchmal möchte ich eine eigene, angepasste Version:
Python: Eigene Zusammenfassung
def erstelle_zusammenfassung(hook_daten):
"""Erstellt eine strukturierte Zusammenfassung"""
sitzung_id = hook_daten.get('session_id')
zusammenfassung = {
'meta': {
'sitzung': sitzung_id,
'komprimiert_am': datetime.now().isoformat(),
'tokens_vor': hook_daten.get('current_context_tokens')
},
'bearbeitete_dateien': hole_bearbeitete_dateien(sitzung_id),
'getroffene_entscheidungen': hole_entscheidungen(sitzung_id),
'offene_fragen': hole_offene_fragen(sitzung_id),
'naechste_schritte': hole_geplante_schritte(sitzung_id)
}
# In menschenlesbarem Format speichern
dateiname = f"/tmp/zusammenfassung_{sitzung_id}.md"
with open(dateiname, 'w') as f:
f.write(formatiere_als_markdown(zusammenfassung))
return dateiname
Diese Zusammenfassung kann ich später wieder einlesen, um den Faden aufzunehmen.
Die Weisheit des Packers
Der weise Packer weiß: Man kann nicht alles mitnehmen. Aber man kann sicherstellen, dass das Wichtigste dabei ist. PreCompact gibt Dir diese Möglichkeit.
Letzte Chance
PreCompact ist wirklich die letzte Chance. Nach der Komprimierung sind die alten Nachrichten nur noch als Zusammenfassung vorhanden. Was Du jetzt nicht sicherst, ist möglicherweise für immer verloren. Nutze diesen Moment.
Was kann ich damit alles machen?
- Kontext sichern: Kritische Informationen und Entscheidungen extern speichern.
- Tasks exportieren: Offene Aufgaben in eine Datei oder Datenbank schreiben.
- Zusammenfassung erstellen: Eigene, strukturierte Zusammenfassung des bisherigen Gesprächs.
- Metriken erfassen: Wie oft wird komprimiert? Bei welchen Themen?
- Benutzer warnen: Hinweis geben, dass wichtiger Kontext verloren gehen könnte.
- Checkpoint erstellen: Vollständigen Sitzungszustand für spätere Wiederherstellung speichern.
Zusammenfassung
PreCompact ist der Koffer-Moment: Du packst das Wichtigste ein, bevor die Reise weitergeht. Nutze diesen Hook, um kritische Informationen zu retten. Was Du jetzt sicherst, steht Dir auch nach der Komprimierung zur Verfügung. Was Du nicht sicherst, ist möglicherweise für immer weg.
Zusammenfassung: Alle Events im Lebenszyklus
Hier noch einmal der vollständige Überblick über alle Hook-Events und wann sie feuern:
Der Lebenszyklus einer Claude-Code-Sitzung
╔═══════════════════════════════════════════════════════════════╗
║ SESSION LIFECYCLE ║
╠═══════════════════════════════════════════════════════════════╣
║ SessionStart ─────────────────────────────────────────────── ║
║ │ ║
║ ▼ ║
║ ┌─────────────────────────────────────────────────────────┐ ║
║ │ UserPromptSubmit │ ║
║ │ │ │ ║
║ │ ▼ │ ║
║ │ ┌─────────────────────────────────────────────────┐ │ ║
║ │ │ PreToolUse ──▶ Tool ──▶ PostToolUse │ │ ║
║ │ │ ▲ │ │ │ ║
║ │ │ └────────────────────┘ (wiederholen) │ │ ║
║ │ └─────────────────────────────────────────────────┘ │ ║
║ │ │ ║
║ │ Optionale Events: │ ║
║ │ ├─ Stop (bei Abbruch) │ ║
║ │ ├─ SubagentStop (bei Sub-Task-Ende) │ ║
║ │ ├─ Notification (bei System-Events) │ ║
║ │ ├─ PermissionRequest (bei Berechtigungsanfrage) │ ║
║ │ └─ PreCompact (vor Kontext-Komprimierung) │ ║
║ └─────────────────────────────────────────────────────────┘ ║
║ │ ║
║ ▼ ║
║ SessionEnd ───────────────────────────────────────────────── ║
╚═══════════════════════════════════════════════════════════════╝
Jedes Event hat seinen Platz und seine Aufgabe. Zusammen bilden sie ein mächtiges System, mit dem Du Claude Code an Deine Bedürfnisse anpassen kannst.
Praxisbeispiel: Wie ich mit dem Regelwerk zuerst immens viele Token verbraucht und dann sehr viel gespart habe
Ich habe einen sehr gewissenhaften Assistenten gebaut. Dieser Assistent prüft bei jeder Handlung, ob bestimmte Regeln eingehalten werden. Das ist grundsätzlich eine gute Sache, schließlich möchte ich keine Fehler machen.
Nun ist es aber so: Dieser Assistent hat ein sehr dickes Regelbuch mit über 200 Regeln. Und jedes Mal, wenn auch nur ein Buchstabe geändert wird, holt er das komplette Buch hervor, blättert durch alle 200 Seiten und prüft jede einzelne Regel.
Das Problem dabei? Die meisten Regeln sind in dem Moment völlig irrelevant. Wenn gerade ein deutscher Text geschrieben wird, braucht es keine Regeln für französische Grammatik. Wenn an einer Tabellenkalkulation gearbeitet wird, sind die Regeln für Bildbearbeitung unwichtig.
Die Ausgangssituation
Mein Prüfsystem lädt bei jeder Änderung alle 200+ Regeln aus der Datenbank. Erst danach wird im Programm gefiltert, welche Regeln tatsächlich relevant sind. Das ist etwa so, als würde ein Bibliothekar bei jeder Frage erst einmal alle Bücher aus dem Regal holen und dann die meisten ungelesen zurückstellen.
Was genau passiert da eigentlich?
Wenn Claude Code eine Datei bearbeitet, passiert Folgendes:
- Du gibst einen Auftrag: „Ändere bitte diese PHP-Datei."
- Claude bereitet die Änderung vor: Das Werkzeug „Bearbeiten" wird aktiviert.
- Der Ereignis-Auslöser feuert: „PreToolUse", also „Vor der Werkzeug-Nutzung".
- Das Prüfsystem springt an: Es soll sicherstellen, dass die Änderung keine Regeln verletzt.
- Hier wird es ineffizient: Das System lädt ALLE Regeln, obwohl nur ein Bruchteil relevant ist.
In Zahlen ausgedrückt: Bei einer typischen Arbeitssitzung mit 50 Dateiänderungen werden über 10.000 Regeln geladen, von denen mehr als 9.000 sofort wieder verworfen werden. Das ist nicht nur langsam, sondern auch verschwenderisch.
Ein Blick unter die Motorhaube
Für die technisch Interessierten zeige ich hier, wie die ursprüngliche Datenbankabfrage aussah. Aber auch ohne Programmierkenntnisse ist die Struktur leicht zu verstehen:
SQL: Die alte Abfrage
-- Die alte Abfrage: Hole ALLES
SELECT regeln.*, vertraege.schluessel
FROM regeln
JOIN vertraege ON regeln.vertrag_id = vertraege.id
WHERE vertraege.status = 'aktiv'
AND regeln.ist_aktiv = 1
-- Ergebnis: ~200 Datensätze
-- Davon relevant: ~10-20
-- Verschwendung: ~90%
Die Abfrage sagt im Grunde: „Gib mir alle aktiven Regeln aus allen aktiven Verträgen." Das ist etwa so, als würdest Du in einer Bibliothek sagen: „Gib mir alle Bücher, die nicht aussortiert wurden." Nicht besonders zielführend, wenn Du eigentlich nur ein Kochbuch suchst.
Die Filterung kam viel zu spät
Was passierte nach dem Laden aller Regeln? Das Programm musste diese Arbeit erledigen:
- Prüfen, ob die Regel für diese Dateiendung gilt (.php, .py, .js ...)
- Prüfen, ob die Regel für diesen Dateipfad gilt (src/, tests/, ...)
- Prüfen, ob die Regel für dieses Werkzeug gilt (Bearbeiten, Schreiben, ...)
- Prüfen, ob die Regel für dieses Projekt gilt
All diese Prüfungen hätten bereits in der Datenbank stattfinden können. Stattdessen wurden die Daten erst übertragen, dann im Programm gefiltert. Das ist, als würdest Du im Restaurant erst alle Gerichte der Speisekarte bestellen und dann bei der Lieferung sagen: „Ach, ich bin eigentlich Vegetarier."
Das eigentliche Problem
Je später im Ablauf gefiltert wird, desto mehr Ressourcen werden verschwendet. Daten müssen übertragen, zwischengespeichert und verarbeitet werden, nur um dann festzustellen, dass sie nicht gebraucht werden.
Die Idee: Früher und präziser filtern
Die Lösung liegt auf der Hand: Ich muss der Datenbank beibringen, von Anfang an nur die relevanten Regeln herauszugeben. Dafür brauche ich zusätzliche Informationen in der Datenbank, quasi Etiketten, die jeder Regel sagen, wann und wo sie gebraucht wird.
Das ist wie ein gut organisiertes Gewürzregal. Statt alle Gewürze auf einen Haufen zu werfen, sortierst Du sie:
- Nach Küche: Italienisch, Asiatisch, Mexikanisch
- Nach Gericht: Suppe, Braten, Salat
- Nach Schärfegrad: Mild, Mittel, Scharf
Wenn Du dann einen milden italienischen Salat zubereitest, greifst Du gezielt in die richtige Ecke und nicht erst durch alle Gewürze.
Die sechs Filterebenen
Ich habe das Regelwerk in sechs Ebenen organisiert. Jede Ebene filtert präziser:
Von grob zu fein
| Ebene | Fragestellung | Beispielwerte |
|---|---|---|
| 1. Ereignis | Bei welchem Ereignis prüfen? | Sitzungsstart, Vor Werkzeug, Nach Werkzeug |
| 2. Wann | Wann genau wird geprüft? | vor_werkzeug, nach_werkzeug, sitzungsstart |
| 3. Wo | Wo gilt die Regel? | global, projektspezifisch |
| 4. Werkzeug | Bei welchem Werkzeug? | Bearbeiten, Schreiben, Lesen, Konsole |
| 5. Dateitypen | Für welche Dateitypen? | .php, .py, .js, .css |
| 6. Verzeichnisse | In welchen Verzeichnissen? | src/, tests/, templates/ |
Diese sechs Ebenen arbeiten wie ein Trichter: Jede Ebene reduziert die Anzahl der übrig bleibenden Regeln weiter, bis am Ende nur noch die wirklich relevanten übrig sind.
Was musste ich dafür ändern?
Die Datenbank brauchte zwei neue Informationsfelder. In der Fachsprache nennt man das „Spalten hinzufügen". Hier ist, was ich ergänzt habe:
SQL: Schema-Erweiterung
-- Neue Information 1: Bei welchem Ereignis wird diese Regel geprüft?
ALTER TABLE regeln
ADD COLUMN ausloeser_typ ENUM('vor_werkzeug','nach_werkzeug','sitzungsstart')
DEFAULT 'vor_werkzeug';
-- Neue Information 2: Welche Werkzeuge lösen diese Regel aus?
ALTER TABLE regeln
ADD COLUMN ausloeser_werkzeuge JSON DEFAULT NULL;
-- Verzeichnis für schnelles Nachschlagen
CREATE INDEX idx_ausloeser
ON regeln(ausloeser_typ, ist_aktiv);
Was bedeutet das in einfachen Worten?
- ausloeser_typ: Jede Regel bekommt einen Stempel, der sagt: „Ich werde VOR der Werkzeugnutzung geprüft" oder „Ich werde NACH der Werkzeugnutzung geprüft" oder „Ich werde beim Sitzungsstart geprüft."
- ausloeser_werkzeuge: Jede Regel bekommt eine Liste von Werkzeugen, für die sie gilt. Eine Regel könnte sagen: „Ich bin nur relevant, wenn das Bearbeiten-Werkzeug verwendet wird."
- Der Index: Das ist wie ein Stichwortverzeichnis in einem Buch. Es hilft der Datenbank, schneller die richtigen Regeln zu finden.
Warum diese Unterscheidung wichtig ist
Du fragst Dich vielleicht: „Warum brauchen wir sowohl einen Geltungsbereich als auch einen Auslöser-Typ? Ist das nicht das Gleiche?"
Gute Frage! Diese beiden Dimensionen sind tatsächlich unabhängig voneinander, sie beantworten unterschiedliche Fragen:
| Dimension | Beantwortet die Frage | Beispiel |
|---|---|---|
| Geltungsbereich | WO gilt die Regel? | „Diese Regel gilt überall" vs. „Diese Regel gilt nur im Projekt XY" |
| Auslöser-Typ | WANN wird geprüft? | „Prüfe VOR der Änderung" vs. „Prüfe NACH der Änderung" |
| Werkzeuge | Bei WELCHER Aktion? | „Nur beim Bearbeiten" vs. „Nur bei Konsolenbefehlen" |
Ein konkretes Beispiel: Eine Regel könnte so definiert sein:
- Geltungsbereich: global (gilt in allen Projekten)
- Auslöser-Typ: nach_werkzeug (wird nach der Ausführung geprüft)
- Werkzeuge: [„Konsole"] (nur bei Konsolenbefehlen)
Diese Regel würde also überall gelten, aber nur nach der Ausführung von Konsolenbefehlen geprüft werden. Die drei Dimensionen ergänzen sich, ohne sich zu überschneiden.
Die neue, optimierte Abfrage
Mit den zusätzlichen Informationen kann die Datenbank jetzt viel gezielter arbeiten. Hier ist die neue Abfrage, wieder mit Erklärungen:
Python: Optimierte Regelabfrage
def hole_relevante_regeln(projekt, dateiendung, dateipfad, werkzeug, ausloeser_typ='vor_werkzeug'):
"""
Lädt nur die Regeln, die wirklich gebraucht werden.
Parameter:
- projekt: In welchem Projekt arbeiten wir?
- dateiendung: Welche Dateiendung hat die Datei? (.php, .py, ...)
- dateipfad: Wo liegt die Datei? (src/..., tests/..., ...)
- werkzeug: Welches Werkzeug wird verwendet? (Bearbeiten, Schreiben, ...)
- ausloeser_typ: Wann wird geprüft? (vor_werkzeug, nach_werkzeug, ...)
"""
abfrage = """
SELECT r.*, v.schluessel, v.name AS vertragsname
FROM regeln r
JOIN vertraege v ON r.vertrag_id = v.id
WHERE v.status = 'aktiv'
AND r.ist_aktiv = 1
-- Filter 1: Nur Regeln für diesen Auslöser-Typ
AND r.ausloeser_typ = %s
-- Filter 2: Nur Regeln für dieses Werkzeug (oder alle, wenn nicht eingeschränkt)
AND (r.ausloeser_werkzeuge IS NULL
OR JSON_CONTAINS(r.ausloeser_werkzeuge, %s))
-- Filter 3: Nur Regeln für diese Dateiendung (oder alle, wenn nicht eingeschränkt)
AND (r.gilt_fuer_endungen IS NULL
OR JSON_CONTAINS(r.gilt_fuer_endungen, %s))
-- Filter 4: Nur Regeln für dieses Projekt (oder globale Regeln)
AND (v.geltungsbereich = 'global'
OR JSON_CONTAINS(v.projekt_schluessel, %s))
ORDER BY r.schweregrad DESC
"""
cursor.execute(abfrage, (
ausloeser_typ,
json.dumps(werkzeug),
json.dumps(dateiendung),
json.dumps(projekt)
))
return cursor.fetchall()
Was passiert hier? Die Datenbank filtert jetzt selbst:
- Nur Regeln, die zum aktuellen Zeitpunkt geprüft werden sollen
- Nur Regeln, die für das verwendete Werkzeug relevant sind
- Nur Regeln, die für den Dateityp gelten
- Nur Regeln, die im aktuellen Projekt oder überall gelten
Das Ergebnis: Statt 200 Regeln kommen nur noch 10 bis 20 zurück. Die restlichen 180+ werden gar nicht erst übertragen.
Wie habe ich das umgestellt?
Eine wichtige Frage bei solchen Änderungen ist: Wie stellt man ein laufendes System um, ohne dass etwas kaputtgeht? Ich habe das in drei Phasen gemacht:
Phase 1: Struktur erweitern
Zuerst habe ich die neuen Informationsfelder zur Datenbank hinzugefügt, aber mit Standardwerten. Das bedeutet: Alle bestehenden Regeln funktionieren weiter wie bisher. Ein leeres Feld wird als „gilt für alles" interpretiert.
Analogie: Ich baue einen neuen Schrank neben den alten, ohne den alten abzureißen.
Phase 2: Regeln klassifizieren
Dann habe ich jede bestehende Regel mit den neuen Informationen versehen. Bei welchem Ereignis soll sie geprüft werden? Für welche Werkzeuge gilt sie? Vieles davon konnte ich automatisch aus den bestehenden Daten ableiten.
Analogie: Ich beschrifte alle Gewürze im alten Regal, bevor ich sie in das neue, sortierte Regal umräume.
Phase 3: Abfragen umstellen
Erst ganz zum Schluss habe ich das Programm auf die neue, optimierte Abfrage umgestellt. Zu diesem Zeitpunkt waren alle Daten bereits korrekt klassifiziert.
Analogie: Erst wenn alle Gewürze im neuen Regal stehen, ändere ich meine Gewohnheit und greife dorthin.
Diese schrittweise Vorgehensweise nennt man „sanfte Migration". Zu keinem Zeitpunkt war das System in einem kaputten Zustand. Hätte etwas nicht funktioniert, hätte ich jederzeit zur alten Version zurückkehren können.
Was habe ich damit erreicht?
Hier sind die messbaren Verbesserungen:
| Was ich messe | Vorher | Nachher | Verbesserung |
|---|---|---|---|
| Regeln pro PHP-Bearbeitung | ~200 | ~15 | 92% weniger |
| Regeln pro Python-Bearbeitung | ~200 | ~11 | 95% weniger |
| Datenbankabfrage | ~45 ms | ~8 ms | 82% schneller |
| Filterung im Programm | ~120 ms | ~5 ms | 96% schneller |
| Gesamtzeit pro Prüfung | ~165 ms | ~13 ms | 92% schneller |
Bei 50 Änderungen pro Arbeitssitzung spart das etwa 7,5 Sekunden reine Wartezeit. Das klingt vielleicht nicht nach viel, aber mir geht es um etwas anderes: Es werden immens viele Token eingespart. Bei jeder Prüfung fallen 90% weniger Daten an, die verarbeitet werden müssen. Du bleibst mehr im Flow, weil das System schneller reagiert. Und im Schnitt bist Du einfach 90% effizienter unterwegs. Den kleinen Schwaben in mir freut so etwas ungemein.
Der Hebel-Effekt
Eine kleine Optimierung an der richtigen Stelle, nämlich dort, wo die Daten gefiltert werden, hat einen überproportional großen Effekt. Das liegt daran, dass diese Stelle bei jeder einzelnen Operation durchlaufen wird. Je früher im Ablauf Du optimierst, desto größer der Hebel.
Was ich daraus gelernt habe
Diese Optimierung hat mir einige wertvolle Erkenntnisse gebracht, die auch für andere Situationen gelten:
- Filtere so früh wie möglich: Je früher im Ablauf Du filterst, desto weniger Daten müssen transportiert, gespeichert und verarbeitet werden. Die Datenbank kann das oft besser als das Programm.
- Leere Felder als Platzhalter: Wenn ein neues Informationsfeld leer ist, interpretiere es als „gilt für alles". So bleiben alte Daten kompatibel, während neue Daten präziser sein können.
- Unabhängige Dimensionen: WO (Geltungsbereich), WANN (Auslöser-Typ) und bei WELCHER Aktion (Werkzeuge) sind drei verschiedene Fragen. Beantworte jede separat.
- Schrittweise umstellen: Ändere erst die Struktur, dann die Daten, dann das Programm. Niemals alles auf einmal.
- Messen, messen, messen: Ohne Messungen vor und nach der Änderung wüsste ich nicht, ob die Optimierung tatsächlich etwas gebracht hat.
Zusammenfassung: Alle Ereignisse im Überblick
Zum Abschluss dieses Kapitels und dieses gesamten Artikels über Ereignis-Auslöser hier noch einmal der vollständige Lebenszyklus einer Claude-Code-Sitzung:
Sitzungs-Lebenszyklus
┌─────────────────────────────────────────────────────────────┐
│ SITZUNGS-LEBENSZYKLUS │
├─────────────────────────────────────────────────────────────┤
│ Sitzungsstart (SessionStart) │
│ ↓ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Eingabe-Übermittlung (UserPromptSubmit) │ │
│ │ ↓ │ │
│ │ ┌─────────────────────────────────────────────┐ │ │
│ │ │ Vor-Werkzeug → Werkzeug → Nach-Werkzeug │ │ │
│ │ │ ↑ ↓ │ │ │
│ │ │ └──────────────┘ (wiederholen) │ │ │
│ │ └─────────────────────────────────────────────┘ │ │
│ │ ↓ │ │
│ │ [Stopp] (bei Abbruch durch Benutzer) │ │
│ │ [Unteragent-Stopp] (bei Ende eines Unteragenten) │ │
│ │ [Benachrichtigung] (bei Systemmeldungen) │ │
│ │ [Berechtigungsanfrage] (bei sensiblen Aktionen) │ │
│ │ [Vor-Komprimierung] (bei vollem Kontextspeicher) │ │
│ └─────────────────────────────────────────────────────┘ │
│ ↓ │
│ Sitzungsende (SessionEnd) │
└─────────────────────────────────────────────────────────────┘
Jedes dieser Ereignisse bietet Dir die Möglichkeit, in den Ablauf einzugreifen, sei es zur Validierung, zur Protokollierung, zur Optimierung oder zur Automatisierung. Die Kunst liegt darin, das richtige Ereignis für den richtigen Zweck zu wählen.
Der Schlüssel zum Verständnis
Ereignis-Auslöser sind wie Türklingeln an verschiedenen Stellen eines Gebäudes. Wenn Du weißt, welche Klingel wann ertönt, kannst Du zur richtigen Zeit am richtigen Ort sein und genau das tun, was in diesem Moment gebraucht wird.
Ich freue mich, dass Du Dir die Zeit genommen hast, bis hierher zu lesen (das ist mittlerweile etwas Besonderes)!
Liebe Grüße, Dein Karl
P.S.: Wenn Du noch mehr über solche Themen erfahren möchtest, dann komm doch in die KI-Gemeinschaft!