Claude Code Session-Hooks
Vier Claude Code Session-Lifecycle-Hooks: Init bei Bedarf ausführen, Projektkontext bei SessionStart injizieren, Transkripte sichern und Cleanup beim SessionEnd-Exit protokollieren.
Hören Sie auf zu konfigurieren. Fangen Sie an zu bauen.
SaaS-Builder-Vorlagen mit KI-Orchestrierung.
Problem: Jede neue Session startet blind. Du erklärst erneut den Branch, auf dem du bist, die Task-Queue und die Env-Vars, die deine Skripte brauchen. Wenn die Session endet, passiert das Cleanup, das hätte passieren sollen, nie.
Quick Win: Füge das in settings.json ein. Git-Kontext landet beim Start in jedem Chat:
{
"hooks": {
"SessionStart": [
{
"hooks": [
{
"type": "command",
"command": "echo '## Git' && git branch --show-current && git status --short | head -10"
}
]
}
]
}
}Jede Session öffnet sich jetzt mit Kontext. Null manuelle Einrichtung.
Die vier Session-Lifecycle-Hooks
Session-Verhalten wird von vier Hook-Typen gesteuert:
| Hook | Wann er feuert | Kann blockieren? | Anwendungsfall |
|---|---|---|---|
| Setup | Mit --init oder --maintenance | NEIN | Einmalige Einrichtung, Migrationen |
| SessionStart | Jeder Session-Start/Resume | NEIN | Kontext laden, Env-Vars setzen |
| PreCompact | Vor der Kontext-Komprimierung | NEIN | Transkripte sichern |
| SessionEnd | Session wird beendet | NEIN | Cleanup, Logging |
SessionStart: Kontext jedes Mal laden
SessionStart läuft immer, wenn eine Session beginnt oder fortgesetzt wird. Greif darauf zurück, wenn etwas immer in Claudes Kopf sein soll.
Einfache Kontext-Injektion
{
"hooks": {
"SessionStart": [
{
"hooks": [
{
"type": "command",
"command": "echo '## Project State' && cat .claude/tasks/session-current.md 2>/dev/null || echo 'No active session'"
}
]
}
]
}
}Mit JSON-Ausgabe
Für strukturierte Kontext-Injektion:
#!/usr/bin/env python3
import json
import sys
import subprocess
def get_project_context():
try:
branch = subprocess.check_output(
['git', 'rev-parse', '--abbrev-ref', 'HEAD'],
text=True, stderr=subprocess.DEVNULL
).strip()
status = subprocess.check_output(
['git', 'status', '--porcelain'],
text=True, stderr=subprocess.DEVNULL
).strip()
changes = len(status.split('\n')) if status else 0
except:
branch, changes = "unknown", 0
return f"""=== SESSION CONTEXT ===
Git Branch: {branch}
Uncommitted Changes: {changes}
=== END ===""".strip()
output = {
"hookSpecificOutput": {
"hookEventName": "SessionStart",
"additionalContext": get_project_context()
}
}
print(json.dumps(output))
sys.exit(0)SessionStart-Matcher
Auf spezifische Session-Events abzielen:
{
"hooks": {
"SessionStart": [
{
"matcher": "startup",
"hooks": [{ "type": "command", "command": "echo 'Fresh session'" }]
},
{
"matcher": "resume",
"hooks": [{ "type": "command", "command": "echo 'Resumed session'" }]
},
{
"matcher": "compact",
"hooks": [{ "type": "command", "command": "echo 'Post-compaction'" }]
}
]
}
}startup- Neue Sessionresume- Von--resume,--continueoder/resumeclear- Nach/clearcompact- Nach Komprimierung
Umgebungsvariablen persistieren
SessionStart hat Zugriff auf CLAUDE_ENV_FILE zum Setzen von session-weiten Umgebungsvariablen:
#!/bin/bash
# Persist environment changes from nvm, pyenv, etc.
ENV_BEFORE=$(export -p | sort)
# Setup commands that modify environment
source ~/.nvm/nvm.sh
nvm use 20
if [ -n "$CLAUDE_ENV_FILE" ]; then
ENV_AFTER=$(export -p | sort)
comm -13 <(echo "$ENV_BEFORE") <(echo "$ENV_AFTER") >> "$CLAUDE_ENV_FILE"
fi
exit 0Alles, was in CLAUDE_ENV_FILE geschrieben wird, erscheint in jedem Bash-Befehl, den Claude danach ausführt.
Setup: Einmalige Operationen
Setup-Hooks laufen nur, wenn du explizit --init, --init-only oder --maintenance aufrufst. Gut für Arbeit, die du nicht bei jeder neuen Session ausführen willst.
Wann Setup vs. SessionStart nutzen
| Operation | Setup nutzen | SessionStart nutzen |
|---|---|---|
| Abhängigkeiten installieren | Ja | Nein |
| Datenbank-Migrationen ausführen | Ja | Nein |
| Git-Status laden | Nein | Ja |
| Umgebungsvariablen setzen | Ja | Ja |
| Projektkontext injizieren | Nein | Ja |
| Temp-Dateien aufräumen | Ja (maintenance) | Nein |
Setup-Konfiguration
{
"hooks": {
"Setup": [
{
"matcher": "init",
"hooks": [
{
"type": "command",
"command": "npm install && npm run db:migrate"
}
]
},
{
"matcher": "maintenance",
"hooks": [
{
"type": "command",
"command": "npm prune && npm dedupe && rm -rf .cache"
}
]
}
]
}
}Aufrufen mit:
claude --init # Runs 'init' matcher
claude --init-only # Runs 'init' matcher, then exits
claude --maintenance # Runs 'maintenance' matcherSetup-Hooks können auch in CLAUDE_ENV_FILE schreiben, um Umgebungsvariablen zu persistieren.
PreCompact: Vor dem Kontextverlust
PreCompact feuert kurz vor der Komprimierung, egal ob der Nutzer sie mit /compact ausgelöst hat oder sie automatisch startete, als das Fenster sich füllte.
Transkripte sichern
#!/usr/bin/env python3
import json
import sys
import shutil
from pathlib import Path
from datetime import datetime
input_data = json.load(sys.stdin)
transcript_path = input_data.get('transcript_path', '')
trigger = input_data.get('trigger', 'unknown')
if transcript_path and Path(transcript_path).exists():
backup_dir = Path('.claude/backups')
backup_dir.mkdir(parents=True, exist_ok=True)
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
backup_name = f"transcript_{trigger}_{timestamp}.jsonl"
shutil.copy2(transcript_path, backup_dir / backup_name)
# Keep only last 10 backups
backups = sorted(backup_dir.glob('transcript_*.jsonl'))
for old_backup in backups[:-10]:
old_backup.unlink()
sys.exit(0)PreCompact-Matcher
{
"hooks": {
"PreCompact": [
{
"matcher": "auto",
"hooks": [{ "type": "command", "command": "echo 'Auto-compacting...'" }]
},
{
"matcher": "manual",
"hooks": [{ "type": "command", "command": "echo 'Manual /compact'" }]
}
]
}
}auto- Kontextfenster voll, automatische Komprimierungmanual- Nutzer hat/compactausgeführt
Recovery-Marker erstellen
Kombiniere PreCompact mit SessionStart, um Kontext nach einer Komprimierung wieder aufzubauen. Ein funktionierendes Pattern: ein gemeinsames Backup-Core-Modul, ein Statusline-Monitor, der schwellenwertbasierte Trigger feuert, und ein PreCompact-Handler, alle koordiniert durch eine State-Datei, damit nichts zwischen Sessions verloren geht. Schau dir den Context-Recovery-Hook-Leitfaden für den vollständigen Walkthrough an.
SessionEnd: Cleanup
SessionEnd läuft, wenn eine Session auf dem Weg raus ist. Es kann das Herunterfahren nicht blockieren, aber es kann Cleanup erledigen.
Session-Stats protokollieren
#!/usr/bin/env python3
import json
import sys
from pathlib import Path
from datetime import datetime
input_data = json.load(sys.stdin)
session_id = input_data.get('session_id', 'unknown')
reason = input_data.get('reason', 'unknown')
log_dir = Path('.claude/logs')
log_dir.mkdir(parents=True, exist_ok=True)
log_entry = {
"session_id": session_id,
"ended_at": datetime.now().isoformat(),
"reason": reason
}
with open(log_dir / 'session-history.jsonl', 'a') as f:
f.write(json.dumps(log_entry) + '\n')
sys.exit(0)SessionEnd-Gründe
Das reason-Feld sagt dir, warum die Session beendet wurde:
clear- Nutzer hat/clearausgeführtlogout- Nutzer hat sich ausgeloggtprompt_input_exit- Nutzer hat beendet, während der Prompt sichtbar warother- Andere Exit-Gründe
Vollständiges Lifecycle-Beispiel
Eine vollständige Lifecycle-Konfiguration von Ende zu Ende verdrahtet:
{
"hooks": {
"Setup": [
{
"matcher": "init",
"hooks": [
{
"type": "command",
"command": "npm install && echo 'Dependencies installed'"
}
]
}
],
"SessionStart": [
{
"hooks": [
{
"type": "command",
"command": "echo '## Context' && git status --short && echo '## Tasks' && cat .claude/tasks/session-current.md 2>/dev/null | head -20"
}
]
}
],
"PreCompact": [
{
"hooks": [
{
"type": "command",
"command": "cp \"$CLAUDE_TRANSCRIPT_PATH\" .claude/backups/last-transcript.jsonl 2>/dev/null || true"
}
]
}
],
"SessionEnd": [
{
"hooks": [
{
"type": "command",
"command": "echo \"Session ended: $(date)\" >> .claude/logs/sessions.log"
}
]
}
]
}
}Input-Payloads
SessionStart-Input
{
"session_id": "abc123",
"hook_event_name": "SessionStart",
"source": "startup",
"model": "claude-sonnet-4-20250514",
"cwd": "/path/to/project"
}Setup-Input
{
"session_id": "abc123",
"hook_event_name": "Setup",
"trigger": "init",
"cwd": "/path/to/project"
}PreCompact-Input
{
"session_id": "abc123",
"hook_event_name": "PreCompact",
"transcript_path": "~/.claude/projects/.../transcript.jsonl",
"trigger": "auto",
"custom_instructions": ""
}SessionEnd-Input
{
"session_id": "abc123",
"hook_event_name": "SessionEnd",
"reason": "clear",
"cwd": "/path/to/project"
}Best Practices
-
SessionStart schnell halten - Er läuft bei jeder Session. Schwere Arbeit in Setup verschieben.
-
Setup für einmalige Arbeit nutzen - Abhängigkeits-Installationen, Migrationen, erstmaliges Projekt-Bootstrap.
-
Vor der Komprimierung sichern - PreCompact ist deine letzte Chance, das Transkript zu greifen.
-
Session-Enden protokollieren - SessionEnd ist praktisch für Analytics und Debugging.
-
Sorgfältig matchen - Unterschiedliches Verhalten für
startupvs.resumevs.compactvermeidet Überraschungen.
Nächste Schritte
- Richte den Haupt-Hooks-Leitfaden für alle 12 Hooks ein
- Konfiguriere Context Recovery für das Überleben von Komprimierungen
- Nutze Stop-Hooks für Task-Durchsetzung
- Erkunde Skill-Aktivierung für automatisches Skill-Loading
Hören Sie auf zu konfigurieren. Fangen Sie an zu bauen.
SaaS-Builder-Vorlagen mit KI-Orchestrierung.