Der Stop-Hook von Claude Code führt jedes Mal einen Befehl aus, wenn der Haupt-Agent von Claude mit seiner Antwort fertig ist. Damit ist er der richtige Ort, um eine „Fertig“-Benachrichtigung zu schicken, das Ergebnis zu loggen oder Checks laufen zu lassen. Er läuft nicht, wenn du Claude unterbrichst, und ein Turn, der mit einem API-Fehler endet, löst stattdessen StopFailure aus. Der Hook bekommt die letzte Nachricht von Claude in last_assistant_message und kann decision: "block" mit einem reason zurückgeben, damit Claude weitermacht, statt aufzuhören.
01Wann Stop ausgelöst wird – und wann nicht
Stop wird am Ende jedes Turns ausgelöst, nicht nur, wenn eine ganze Aufgabe erledigt ist. Claude Code kennt keine „Aufgabe“; es weiß nur, dass Claude nicht mehr antwortet und auf dich wartet. Eine kurze Frage löst einmal Stop aus – ein zweistündiges Refactoring ebenfalls.
Er wird nicht ausgelöst:
- wenn du Esc drückst oder den Turn anderweitig unterbrichst;
- wenn der Turn wegen eines API-Fehlers endet, etwa durch ein Rate Limit, Überlastung oder einen Authentifizierungsfehler. Dafür löst Claude Code
StopFailureaus, mit einemerror-Feld, auf das du matchen kannst (rate_limit,overloaded,server_errorund weitere); - wenn ein Subagent fertig ist. Dafür gibt es
SubagentStop.
Stop unterstützt keine Matcher. Jeder Matcher, den du hinzufügst, wird ignoriert, und der Hook läuft bei jedem Turn.
02Der einfachste Stop hook
Füge das in ~/.claude/settings.json ein, um einen Befehl auszuführen, sobald Claude fertig ist. Dieser hier spielt unter macOS einen Sound ab:
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "afplay /System/Library/Sounds/Glass.aiff"
}
]
}
]
}
}
Befehle für Desktop-Benachrichtigungen unter Mac, Windows und Linux findest du unter Benachrichtigung erhalten, wenn Claude Code fertig ist. Im Rest dieses Guides geht es darum, was du mit Input und Output des Hooks anfangen kannst.
03Was der Hook bekommt
Zusätzlich zu den gemeinsamen Feldern (session_id, transcript_path, cwd, permission_mode, hook_event_name) bekommt ein Stop-Hook:
last_assistant_messageDer Text der letzten Antwort von Claude in diesem Turnstop_hook_activetrue, wenn Claude bereits weitermacht, weil ein Stop hook blockiert hatbackground_tasksNoch laufende Tasks, etwa Hintergrund-Shells oder Subagentssession_cronsGeplante Wake-ups in dieser Session, etwa durch /loopNutze last_assistant_message, statt die Transcript-Datei zu lesen. Das Transcript wird asynchron geschrieben und enthält die letzte Nachricht beim Ausführen des Hooks eventuell noch nicht.
background_tasks und session_crons verraten dir, ob die Session wirklich fertig ist oder nur pausiert, bis Hintergrundarbeit sie wieder aufweckt. Ein Benachrichtigungsskript kann prüfen, ob beide leer sind, bevor es „fertig“ meldet.
04Die letzte Nachricht von Claude in die Benachrichtigung packen
Dieses Skript (macOS und Linux, benötigt jq) zeigt den Anfang der Antwort von Claude und bleibt still, solange noch Hintergrundarbeit läuft:
#!/bin/bash
# ~/.claude/hooks/notify-done.sh
input=$(cat)
# Skip if background tasks are still in flight
busy=$(jq '(.background_tasks // []) | length' <<<"$input")
[ "$busy" -gt 0 ] && exit 0
msg=$(jq -r '(.last_assistant_message // "Claude has finished")
| gsub("\\s+"; " ") | .[0:140]' <<<"$input")
if command -v osascript >/dev/null 2>&1; then
osascript -e 'on run argv' \
-e 'display notification (item 1 of argv) with title "Claude has finished"' \
-e 'end run' "$msg"
elif command -v notify-send >/dev/null 2>&1; then
notify-send "Claude has finished" "$msg"
fi
exit 0
Speicher es, führe chmod +x ~/.claude/hooks/notify-done.sh aus und lass den Hook darauf zeigen:
{
"hooks": {
"Stop": [
{
"hooks": [{ "type": "command", "command": "~/.claude/hooks/notify-done.sh" }]
}
]
}
}
Das Skript beendet sich immer mit 0. Das ist wichtig: Bei Stop bedeutet Exit-Code 2 „nicht aufhören“. Ein Benachrichtigungsskript, das versehentlich mit 2 endet, würde Claude also weiterarbeiten lassen.
05Claude weitermachen lassen
Stop gehört zu den Events, die blockieren können. Einen Stop zu blockieren heißt, dass Claude den Turn fortsetzt und deinen Grund als nächste Anweisung nutzt. Dafür gibt es zwei Wege:
- mit
2beenden – Claude bekommt dann dein stderr als Grund; oder - mit
0beenden und JSON ausgeben:
{
"decision": "block",
"reason": "The test suite is failing. Run npm test, fix the failures, then finish."
}
Wenn der Hook eher Hinweise gibt als einen Fehler meldet, hält auch hookSpecificOutput.additionalContext die Unterhaltung am Laufen. Im Transcript wird das dann aber als Stop hook-Feedback statt als Hook-Fehler gekennzeichnet:
{
"hookSpecificOutput": {
"hookEventName": "Stop",
"additionalContext": "Please run the test suite before finishing"
}
}
Endlosschleifen vermeiden
Ein Stop hook, der immer blockiert, würde Claude nie aufhören lassen. Zwei Dinge schützen dich davor:
stop_hook_activeisttrue, sobald Claude wegen eines Stop hook bereits weitermacht. Prüf das und beende den Hook frühzeitig.- Claude Code beendet den Turn trotzdem, wenn Stop-Hooks achtmal hintereinander ohne Tool-Call dazwischen blockiert haben. Mit der Umgebungsvariable
CLAUDE_CODE_STOP_HOOK_BLOCK_CAPkannst du dieses Limit erhöhen.
Hier ist ein Hook, der Claude einmal fehlschlagende Tests reparieren lässt und es danach aufhören lässt:
#!/bin/bash
# ~/.claude/hooks/tests-must-pass.sh
input=$(cat)
[ "$(jq -r '.stop_hook_active' <<<"$input")" = "true" ] && exit 0
if ! npm test --silent >/dev/null 2>&1; then
jq -nc '{decision: "block", reason: "npm test is failing. Fix the failures, then finish."}'
fi
exit 0
Hooks laufen im Arbeitsverzeichnis der Session, npm test läuft also in deinem Projekt. Halte die Checks schnell: Standardmäßig darf ein Command-Hook bis zu 600 Sekunden laufen, und Claude wartet so lange darauf.
Wenn Claude einfach weiter auf ein Ziel hinarbeiten soll, ohne dass du ein Skript schreibst, richtet dir der eingebaute Befehl /goal von Claude Code einen Stop hook für die aktuelle Session ein.
06Benachrichtigungen auch bei Fehlern
Da API-Fehler Stop überspringen, erfährst du über einen „Fertig“-Alert nicht, dass Claude wegen eines Rate Limits aufgegeben hat. Wenn du das mitbekommen willst, trag dasselbe Skript zusätzlich unter StopFailure ein. Ausgabe und Exit-Code werden dort ignoriert, es kann also nur benachrichtigen oder loggen. Bei StopFailure enthält last_assistant_message den Fehlertext, z. B. "API Error: Rate limit reached".
07Wo Claude Code Notifier ins Spiel kommt
Ein Stop hook, der einen Push-Dienst aufruft, ist eine völlig brauchbare Methode, um zu erfahren, dass Claude fertig ist, während du weg bist. Wenn du das selbst bauen willst, ist der ntfy-Guide ein guter Einstieg.
Claude Code Notifier nutzt denselben Stop-Hook, mit einem Unterschied: Er kann etwas zurückschicken. Das kostenlose Kommandozeilentool ccn läuft auf Mac, Windows und Linux und zeigt schon allein eine Desktop-Benachrichtigung an. Mit gekoppelter iPhone- oder Android-App bekommst du einen „Claude ist fertig“-Push, sobald ein Turn endet, und kannst die nächste Anweisung direkt am Handy eintippen. ccn gibt sie aus dem Stop-Hook als nächste Anweisung an Claude zurück, sodass die Session weiterläuft, ohne dass du an der Tastatur sitzt. Hast du in den letzten 30 Sekunden getippt, wird nicht aufs Handy gewartet. Das Tool ist unabhängig und nicht von Anthropic. Mehr zu diesem Workflow findest du unter Lange Tasks laufen lassen und weggehen, die Installation auf der ccn-Seite.
08FAQ
Läuft der Stop hook, wenn Claude die ganze Aufgabe erledigt hat, oder nach jeder Antwort?
Nach jeder Antwort. Stop feuert immer, wenn der Hauptagent mit seiner Antwort fertig ist, egal wie groß die Anfrage war. Prüf background_tasks und session_crons im Input, wenn du wissen musst, ob noch weitere Arbeit ansteht.
Warum hat mein Stop hook nicht gefeuert?
Vielleicht hast du den Turn unterbrochen, dann wird Stop übersprungen, oder er endete mit einem API-Fehler, dann feuert stattdessen StopFailure. Ansonsten prüf, ob /hooks deinen Hook auflistet und das Skript ausführbar ist. Die Troubleshooting-Checkliste deckt den Rest ab.
Warum arbeitet Claude weiter, nachdem mein Stop hook gelaufen ist?
Dein Hook blockiert den Stop, entweder per Exit-Code 2 oder durch die Ausgabe decision: "block". Sorg dafür, dass ein reiner Benachrichtigungs-Hook immer mit 0 beendet wird und jeder blockierende Hook zuerst stop_hook_active prüft.
Wie bekomme ich die letzte Antwort von Claude im Hook?
Lies last_assistant_message aus dem JSON auf stdin, zum Beispiel mit jq -r '.last_assistant_message'. Verlass dich nicht auf transcript_path: Die Datei enthält die letzte Nachricht möglicherweise noch nicht, wenn Stop feuert.
Was ist der Unterschied zwischen Stop und SubagentStop?
Stop gilt für den Hauptagenten. SubagentStop feuert, wenn ein Subagent fertig ist, matcht auf den Agententyp und unterstützt dieselben Blocking-Felder. Ein „Claude ist fertig“-Alert gehört an Stop.