Hook Stop w Claude Code uruchamia komendę za każdym razem, gdy główny agent Claude skończy odpowiadać, więc to dobre miejsce na powiadomienie „gotowe”, zalogowanie wyniku czy odpalenie testów. Nie uruchamia się, gdy przerwiesz Claude, a tura zakończona błędem API wywołuje zamiast niego StopFailure. Hook dostaje ostatnią wiadomość Claude w last_assistant_message i może zwrócić decision: "block" z polem reason, żeby Claude zamiast się zatrzymać, pracował dalej.
01Kiedy Stop się uruchamia, a kiedy nie
Stop uruchamia się na końcu każdej tury, nie tylko po ukończeniu całego zadania. Claude Code nie wie, czym jest „zadanie”; wie tylko, że Claude przestał odpowiadać i czeka na ciebie. Krótkie pytanie daje jeden Stop, tak samo jak dwugodzinny refaktoring.
Nie uruchamia się:
- gdy naciśniesz Esc albo w inny sposób przerwiesz turę;
- gdy tura kończy się błędem API, np. rate limitem, przeciążeniem albo błędem uwierzytelniania. W takich przypadkach Claude Code wywołuje
StopFailurez polemerror, po którym możesz filtrować (rate_limit,overloaded,server_errori inne); - gdy kończy subagent. Do tego służy
SubagentStop.
Stop nie obsługuje matcherów, więc każdy dodany matcher jest ignorowany, a hook odpala się przy każdej turze.
02Najprostszy Stop hook
Dodaj to do ~/.claude/settings.json, żeby uruchamiać komendę za każdym razem, gdy Claude skończy. Ten przykład odtwarza dźwięk na macOS:
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "afplay /System/Library/Sounds/Glass.aiff"
}
]
}
]
}
}
Komendy do powiadomień na pulpicie dla Maca, Windows i Linux znajdziesz w poradniku jak dostać powiadomienie, gdy Claude Code skończy. Reszta tego poradnika opisuje, co możesz zrobić z wejściem i wyjściem hooka.
03Co dostaje hook
Oprócz wspólnych pól (session_id, transcript_path, cwd, permission_mode, hook_event_name) hook Stop dostaje:
last_assistant_messageTekst ostatniej odpowiedzi Claude w tej turzestop_hook_activetrue, jeśli Claude już kontynuuje, bo Stop hook zablokował zatrzymaniebackground_tasksZadania, które wciąż działają, np. shelle w tle albo subagentysession_cronsZaplanowane wybudzenia w tej sesji, np. z /loopKorzystaj z last_assistant_message zamiast czytać plik transkryptu. Transkrypt jest zapisywany asynchronicznie i w momencie uruchomienia hooka może jeszcze nie zawierać ostatniej wiadomości.
background_tasks i session_crons mówią ci, czy sesja naprawdę się skończyła, czy tylko czeka, aż praca w tle znowu ją obudzi. Skrypt powiadomień może sprawdzić, czy oba są puste, zanim zgłosi „gotowe”.
04Wstaw ostatnią wiadomość Claude do powiadomienia
Ten skrypt (macOS i Linux, wymaga jq) pokazuje początek odpowiedzi Claude i milczy, jeśli w tle wciąż coś działa:
#!/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
Zapisz go, uruchom chmod +x ~/.claude/hooks/notify-done.sh i wskaż go w hooku:
{
"hooks": {
"Stop": [
{
"hooks": [{ "type": "command", "command": "~/.claude/hooks/notify-done.sh" }]
}
]
}
}
Skrypt zawsze kończy się kodem 0. To ważne: przy Stop kod wyjścia 2 oznacza „nie zatrzymuj się”, więc skrypt powiadomień, który przypadkiem zwróci 2, nie pozwoli Claude przestać pracować.
05Jak sprawić, żeby Claude pracował dalej
Stop to jedno ze zdarzeń, które mogą blokować. Zablokowanie zatrzymania oznacza, że Claude kontynuuje turę, traktując twój powód jako kolejną instrukcję. Możesz to zrobić na dwa sposoby:
- zakończ z kodem
2, a Claude dostanie twój stderr jako powód; albo - zakończ z kodem
0i wypisz JSON:
{
"decision": "block",
"reason": "The test suite is failing. Run npm test, fix the failures, then finish."
}
Jeśli hook daje wskazówki, a nie zgłasza błąd, hookSpecificOutput.additionalContext też podtrzymuje rozmowę, ale transkrypt oznacza to jako feedback od Stop hook, a nie błąd hooka:
{
"hookSpecificOutput": {
"hookEventName": "Stop",
"additionalContext": "Please run the test suite before finishing"
}
}
Jak uniknąć nieskończonej pętli
Stop hook, który zawsze blokuje, nigdy nie pozwoli Claude się zatrzymać. Chronią cię przed tym dwie rzeczy:
stop_hook_activema wartośćtrue, gdy Claude już kontynuuje z powodu Stop hook. Sprawdź to pole i zakończ wcześniej.- Claude Code i tak kończy turę, jeśli Stop hooki zablokują osiem razy z rzędu bez żadnego wywołania narzędzia pomiędzy. Zmienna środowiskowa
CLAUDE_CODE_STOP_HOOK_BLOCK_CAPpozwala podnieść ten limit.
Oto hook, który raz każe Claude naprawić nieprzechodzące testy, a potem pozwala mu się zatrzymać:
#!/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
Hooki działają w katalogu roboczym sesji, więc npm test uruchomi się w twoim projekcie. Dbaj o to, żeby sprawdzenia były szybkie: domyślnie command hook może działać do 600 sekund, a Claude na niego czeka.
Jeśli chcesz po prostu, żeby Claude dalej pracował nad celem bez pisania skryptu, wbudowana komenda /goal w Claude Code sama ustawi Stop hook na czas sesji.
06Powiadomienia także o błędach
Ponieważ błędy API pomijają Stop, alert „gotowe” nie powie ci, że Claude poddał się przez rate limit. Jeśli chcesz o tym wiedzieć, dodaj ten sam skrypt pod StopFailure. Jego output i exit code są ignorowane, więc może tylko powiadamiać albo logować. Przy StopFailure pole last_assistant_message zawiera treść błędu, np. "API Error: Rate limit reached".
07Gdzie pasuje Claude Code Notifier
Stop hook, który wywołuje usługę push, to całkiem dobry sposób, żeby dowiedzieć się, że Claude skończył, gdy cię nie ma. Jeśli chcesz to zrobić sam, dobrym punktem wyjścia jest poradnik o ntfy.
Claude Code Notifier korzysta z tego samego hooka Stop, z jedną różnicą: potrafi też coś odesłać. Jego darmowe narzędzie CLI, ccn, działa na Macu, Windows i Linux i samo pokazuje powiadomienie na pulpicie. Po sparowaniu aplikacji na iPhone lub Android dostajesz push „Claude skończył” w chwili, gdy kończy się tura, i możesz wpisać kolejne polecenie na telefonie. ccn zwraca je z hooka Stop jako następne polecenie dla Claude, więc sesja toczy się dalej bez ciebie przy klawiaturze. Jeśli pisałeś coś w ciągu ostatnich 30 sekund, nic nie czeka na telefon. To niezależne narzędzie, niestworzone przez Anthropic. Ten workflow opisuje poradnik o odchodzeniu od długich zadań, a instalację znajdziesz na stronie ccn.
08FAQ
Czy Stop hook uruchamia się, gdy Claude skończy całe zadanie, czy po każdej odpowiedzi?
Po każdej odpowiedzi. Stop odpala się za każdym razem, gdy główny agent skończy odpowiadać, niezależnie od wielkości zadania. Jeśli chcesz wiedzieć, czy zaplanowana jest jeszcze jakaś praca, sprawdź background_tasks i session_crons w danych wejściowych.
Dlaczego mój Stop hook się nie uruchomił?
Może przerwałeś turę, co pomija Stop, albo zakończyła się błędem API, co zamiast tego odpala StopFailure. W przeciwnym razie sprawdź, czy /hooks pokazuje twój hook i czy skrypt ma prawa do wykonania. Resztę obejmuje lista kontrolna rozwiązywania problemów.
Dlaczego Claude dalej pracuje po uruchomieniu mojego Stop hook?
Twój hook blokuje zatrzymanie: albo kończy się kodem 2, albo wypisuje decision: "block". Upewnij się, że hook służący tylko do powiadomień zawsze kończy się kodem 0, a każdy blokujący hook najpierw sprawdza stop_hook_active.
Jak dostać ostateczną odpowiedź Claude w hooku?
Odczytaj last_assistant_message z JSON-a na stdin, np. przez jq -r '.last_assistant_message'. Nie polegaj na transcript_path: w chwili odpalenia Stop plik może jeszcze nie zawierać ostatniej wiadomości.
Czym różni się Stop od SubagentStop?
Stop dotyczy głównego agenta. SubagentStop odpala się, gdy skończy subagent, dopasowuje się po typie agenta i obsługuje te same pola blokujące. Alert „Claude skończył” powinien być na Stop.