Claude Code Stop Hook: Tests vor „fertig“ prüfen
Ein kurzer Stop Hook prüft Tests oder Dateien, bevor Claude Code den Zug beendet.
Inhalt
Claude Code Stop Hook: Tests vor „fertig“ prüfen
Ein „fertig“ von Claude Code heißt nur, dass der Agent seinen Zug beenden will. Es belegt weder einen gelaufenen Test noch ein aktuelles Build-Artefakt. Ein Stop Hook kann unmittelbar davor eine kleine lokale Prüfung ausführen und bei einem klaren Fehler weiterarbeiten lassen. Er ersetzt weder CI noch eine vollständige Teststrategie oder eine manuelle Freigabe.
Stop Hook, CLAUDE.md und CI haben unterschiedliche Aufgaben
- Ein Stop Hook startet eine kurze Prüfung, wenn Claude Code seine Antwort abschließt: etwa
git diff --check, einen gezielten Test oder eine erwartete Datei. - CLAUDE.md beschreibt die Regeln für den Agenten, beispielsweise welche Prüfung nötig ist. Die Datei führt selbst keinen Befehl aus.
- CI läuft nach einem Push oder Pull Request in einer getrennten Umgebung. Sie bleibt die verbindliche Schranke für das Team.
Ein Hook, der bei jedem Turn ein Deployment auslöst, etwas veröffentlicht oder Daten an ein externes System schreibt, ist dafür ungeeignet. Solche Seiteneffekte sind schwer wiederholbar und im Fehlerfall schwer zurückzunehmen.
Einen überprüfbaren Abschluss definieren
Notieren Sie vor der Konfiguration genau vier Punkte:
- Behauptung: Was darf der Agent nach dem Turn sagen? Zum Beispiel: „Der Build wurde erzeugt.“
- Nachweis: Welche kurze lokale Prüfung belegt das? Etwa
npm test -- --runInBandundtest -s dist/app.js. - Erfolg: Beide Prüfungen enden mit Exit-Code
0. - Blockierung: Bei einem Fehler liefert der Hook JSON mit
decision: "block"und einem kurzenreason.
Prüfen Sie die Befehle zuerst ohne Hook. Dauern sie mehrere Minuten oder brauchen sie Netzwerkzugriff, wählen Sie eine fokussiertere lokale Prüfung; den vollständigen Lauf übernimmt CI.
Wenn Claude Code über eine API angebunden ist, öffnen Sie zuerst die aktuelle BetterToken-Anleitung, hinterlegen Ihren eigenen API Key in der Tool-Konfiguration und senden eine kurze Testanfrage. Prüfen Sie danach im Dashboard das erwartete Modell, den Status und die Token-Nutzung. Der lokale Hook braucht den Key nicht; schreiben Sie ihn nie ins Hook-Log.
Einen minimalen Stop Hook einrichten
Legen Sie in .claude/settings.json einen projektbezogenen Stop Hook mit kurzer Laufzeit an:
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "./scripts/check-before-stop.sh",
"timeout": 30
}
]
}
]
}
}
Speichern Sie die Prüfung als scripts/check-before-stop.sh. Die Beispielbefehle sind nur für ein Node-Projekt; ersetzen Sie sie durch die tatsächlich vorhandenen Befehle und Pfade Ihres Repositorys.
#!/usr/bin/env sh
set -eu
if [ -t 0 ]; then
input='{"stop_hook_active":false}'
else
input=$(cat)
fi
stop_hook_active=$(printf '%s' "$input" | node -e '
let raw = "";
process.stdin.on("data", chunk => raw += chunk);
process.stdin.on("end", () => {
try { process.stdout.write(String(Boolean(JSON.parse(raw).stop_hook_active))); }
catch { process.stdout.write("false"); }
});')
block_stop() {
if [ "$stop_hook_active" = "true" ]; then
printf '%s\n' "Stop-Prüfung schlägt weiterhin fehl: $block_reason. Führen Sie sie manuell aus; CI bleibt erforderlich." >&2
exit 0
fi
BLOCK_REASON="$block_reason" node -e 'process.stdout.write(JSON.stringify({decision:"block",reason:`Stop-Prüfung fehlgeschlagen: ${process.env.BLOCK_REASON}`}) + "\n")'
exit 0
}
if ! npm test -- --runInBand >/dev/null 2>&1; then
block_reason='npm test ausführen und den fehlgeschlagenen Test beheben'
block_stop
fi
if ! test -s dist/app.js; then
block_reason='dist/app.js neu erzeugen'
block_stop
fi
printf '%s\n' 'Stop-Prüfung bestanden: Tests und dist/app.js'
exit 0
Machen Sie die Datei ausführbar:
chmod +x scripts/check-before-stop.sh
Nach der aktuellen Claude-Code-Dokumentation kann ein Stop Hook mit Code 0 strukturiertes JSON zurückgeben: decision: "block" verhindert das Beenden des Turns, und reason nennt Claude Code die Ursache. Ein Nicht-Null-Code und ein Timeout haben eine eigene Hook-Fehler-Semantik; verwenden Sie sie daher nicht als einzigen Blockiervertrag. Die Prüfung muss in das konfigurierte Zeitlimit passen.
Das Eingabefeld stop_hook_active ist beim nächsten Stop true, wenn Claude Code schon wegen eines Stop Hooks weiterarbeitet. Die fail-open-Verzweigung schreibt dann eine kurze Warnung nach stderr und liefert 0, statt denselben Fehler blind erneut zu blockieren. Das verhindert eine Endlosschleife, lockert aber nicht die CI-Grenze. Wenn wiederholtes Blockieren nötig ist, definieren Sie einen eigenen Zähler und eine explizite Grenze, statt eine nicht dokumentierte feste Wiederholungszahl anzunehmen.
Den vollständigen Ablauf manuell prüfen
Testen Sie nicht nur, ob das Skript im Terminal startet, sondern alle Zweige:
- Ändern Sie im Skript
dist/app.jsvorübergehend indist/missing.jsund führen Sieprintf '%s\n' '{"stop_hook_active":false}' | ./scripts/check-before-stop.sh; echo $?aus. Erwartet werden JSON mit"decision":"block", ein kurzerreasonund Code0. - Stellen Sie den korrekten Pfad wieder her, erzeugen Sie den Build und wiederholen Sie den Befehl. Erwartet wird Code
0. - Setzen Sie erneut einen fehlenden Pfad, lassen Sie Claude Code eine kleine reversible Änderung ausführen und prüfen Sie, dass
decision: "block"den Stop verhindert und den benannten Grund zurückmeldet. - Ohne den Pfad zu korrigieren, führen Sie
printf '%s\n' '{"stop_hook_active":true}' | ./scripts/check-before-stop.sh; echo $?aus. Erwartet werden eine kurze stderr-Warnung und Code0; damit ist die Anti-Loop-Verzweigung geprüft. - Stellen Sie den Pfad wieder her oder erstellen Sie ein aktuelles Artefakt. Beim nächsten Abschluss muss der Hook mit
0enden und den Turn freigeben.
Dieser Ablauf trennt einen funktionierenden Stop Hook von einem Skript, das im Terminal fehlschlägt, Claude Code aber dennoch stoppen lässt.
Einen fehlgeschlagenen Stop sauber behandeln
Trennen Sie zwei Fälle. Liefert der Hook JSON mit decision: "block", lesen Sie reason: Die geprüfte Bedingung ist regulär fehlgeschlagen. Starten Sie diese Prüfung manuell, beheben Sie Test oder Code, vergewissern Sie sich, dass das Artefakt vom aktuellen Befehl stammt, und wiederholen Sie die Claude-Code-Aufgabe.
Beendet sich der Hook-Befehl selbst mit einem Nicht-Null-Code oder Timeout, ist das ein Ausführungsfehler des Hooks und kein bestätigter reason-Block. Lesen Sie Hook-Fehler und stderr, starten Sie das Skript manuell und korrigieren Sie Pfad, Rechte, Abhängigkeit oder Zeitlimit, bevor Sie erneut testen.
Protokollieren Sie nur Prüfungsname und Ergebnis. Geben Sie weder API Keys noch Inhalte aus .env, vollständige Prompts oder komplette Testlogs aus. Ein bestandenes git diff --check bestätigt keine Fachlogik; eine vorhandene Datei bestätigt keinen frischen Build. Der Hook kann ausschließlich die Behauptungen absichern, die Sie explizit darin prüfen.
Den API-Workflow getrennt halten
Für Claude Code über einen API-Provider erstellen und verwalten Sie den API Key in Ihrem eigenen Konto. Der Hook bleibt dabei lokal: Er braucht keinen Zugriff auf den Key, auf vollständige Prompts oder auf Dashboard-Logs. Prüfen Sie bei einer API-Störung die aktuelle Dokumentation für Base URL und Konfiguration; vermischen Sie diese Diagnose nicht mit dem lokalen Stop-Check.
Quellen
- Claude Code Hooks Reference — geprüft am 22. August 2026
- BetterToken: Claude Code einrichten