Skonfiguruj Stop Hook, aby Claude automatycznie powiadamiał cię przez pulpit, Slack lub Telegram po zakończeniu zadania
Claude Code często potrzebuje kilku minut — albo i dłużej — żeby ukończyć zadanie. Prosisz go o refaktoryzację modułu, odpalenie testów czy przetworzenie paczki plików, a sam przełączasz się na coś innego. Problem? Nie wiesz, kiedy skończył.
Wracasz, a on może już od dziesięciu minut czeka z wynikami. Albo utknął na okienku z potwierdzeniem i czeka na ciebie.
Hook Stop pozwala automatycznie dostawać powiadomienie, gdy Claude skończy pracę lub będzie potrzebował twojej reakcji. Nieważne, w jakim jesteś oknie czy na jakim urządzeniu — powiadomienie do ciebie dotrze.
Claude Code udostępnia zdarzenie Stop — odpala się za każdym razem, gdy Claude się zatrzymuje: skończył zadanie, napotkał błąd albo czeka na input od użytkownika.
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "bash .claude/hooks/notify.sh"
}
]
}
]
}
}
Hook dostaje na stdin JSON z polami stop_reason (powód zatrzymania) i stop_message (ostatnia wiadomość Claude'a). Na ich podstawie można dostosować treść powiadomienia.
Struktura JSON na stdin:
{
"session_id": "xxx",
"stop_reason": "end_turn",
"stop_message": "重构完成,所有测试通过。"
}
Na Linuksie — notify-send, na macOS — osascript. Żadnych zewnętrznych serwisów:
#!/bin/bash
input=$(cat)
reason=$(echo "$input" | jq -r '.stop_reason // "unknown"')
message=$(echo "$input" | jq -r '.stop_message // "Claude 已停止"' | head -c 200)
case "$(uname)" in
Linux)
notify-send "Claude Code" "$message" --urgency=normal
;;
Darwin)
osascript -e "display notification \"$message\" with title \"Claude Code\""
;;
esac
Zaleta: zero konfiguracji, działa od razu.
Wada: powiadomienie przychodzi tylko na bieżącą maszynę — na innym urządzeniu go nie zobaczysz.
Slack Incoming Webhook pozwala wysyłać powiadomienia na wybrany kanał lub w wiadomości prywatnej.
Przygotowanie:
1. Stwórz aplikację na stronie zarządzania App w Slacku
2. Włącz Incoming Webhooks i skopiuj URL webhooka
3. Zapisz URL w zmiennej środowiskowej SLACK_WEBHOOK_URL
#!/bin/bash
input=$(cat)
reason=$(echo "$input" | jq -r '.stop_reason // "unknown"')
message=$(echo "$input" | jq -r '.stop_message // "Claude 已停止"' | head -c 300)
webhook_url="${SLACK_WEBHOOK_URL}"
[[ -z "$webhook_url" ]] && exit 0
# 根据停止原因选 emoji
case "$reason" in
end_turn) emoji="✅" ;;
user_input) emoji="⏳" ;;
*) emoji="🔔" ;;
esac
payload=$(jq -n \
--arg text "$emoji *Claude Code* | \`$reason\`
$message" \
'{text: $text}')
curl -s -X POST "$webhook_url" \
-H 'Content-Type: application/json' \
-d "$payload" > /dev/null 2>&1 &
& na końcu jest kluczowe — curl działa w tle i nie blokuje Claude'a.
Bot telegramowy świetnie sprawdza się do użytku osobistego — za darmo, natychmiast, powiadomienia trafiają prosto na telefon.
Przygotowanie:
1. Stwórz bota przez @BotFather i zdobądź token
2. Ustal swój Chat ID (wyślij botowi wiadomość, potem wywołaj API getUpdates)
3. Ustaw zmienne środowiskowe TELEGRAM_BOT_TOKEN i TELEGRAM_CHAT_ID
#!/bin/bash
input=$(cat)
reason=$(echo "$input" | jq -r '.stop_reason // "unknown"')
message=$(echo "$input" | jq -r '.stop_message // "Claude 已停止"' | head -c 300)
token="${TELEGRAM_BOT_TOKEN}"
chat_id="${TELEGRAM_CHAT_ID}"
[[ -z "$token" || -z "$chat_id" ]] && exit 0
text="🤖 *Claude Code*
状态:\`$reason\`
$message"
curl -s "https://api.telegram.org/bot${token}/sendMessage" \
-d chat_id="$chat_id" \
-d text="$text" \
-d parse_mode="Markdown" > /dev/null 2>&1 &
Jeśli jesteś na tej samej maszynie, ale przełączyłeś okno, dźwięk często działa lepiej niż wyskakujące powiadomienie:
#!/bin/bash
input=$(cat)
reason=$(echo "$input" | jq -r '.stop_reason // "unknown"')
case "$(uname)" in
Linux)
paplay /usr/share/sounds/freedesktop/stereo/complete.oga 2>/dev/null &
;;
Darwin)
afplay /System/Library/Sounds/Glass.aiff &
;;
esac
Można łączyć z innymi opcjami — lokalny dźwięk plus zdalny push.
W praktyce najlepiej zebrać wszystko w jednym skrypcie:
.claude/hooks/notify.sh:
#!/bin/bash
input=$(cat)
reason=$(echo "$input" | jq -r '.stop_reason // "unknown"')
message=$(echo "$input" | jq -r '.stop_message // "Claude 已停止"' | head -c 300)
# 根据原因选 emoji
case "$reason" in
end_turn) emoji="✅"; title="任务完成" ;;
user_input) emoji="⏳"; title="等待输入" ;;
*) emoji="🔔"; title="Claude 已停止" ;;
esac
# 1. 桌面通知(本地)
case "$(uname)" in
Linux) notify-send "Claude Code: $title" "$message" --urgency=normal ;;
Darwin) osascript -e "display notification \"$message\" with title \"Claude Code: $title\"" ;;
esac
# 2. 声音提示
case "$(uname)" in
Linux) paplay /usr/share/sounds/freedesktop/stereo/complete.oga 2>/dev/null & ;;
Darwin) afplay /System/Library/Sounds/Glass.aiff & ;;
esac
# 3. Slack(如果配了 Webhook)
if [[ -n "$SLACK_WEBHOOK_URL" ]]; then
payload=$(jq -n --arg text "$emoji *$title*
$message" '{text: $text}')
curl -s -X POST "$SLACK_WEBHOOK_URL" \
-H 'Content-Type: application/json' \
-d "$payload" > /dev/null 2>&1 &
fi
# 4. Telegram(如果配了 Bot)
if [[ -n "$TELEGRAM_BOT_TOKEN" && -n "$TELEGRAM_CHAT_ID" ]]; then
curl -s "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendMessage" \
-d chat_id="$TELEGRAM_CHAT_ID" \
-d text="$emoji *$title*
$message" \
-d parse_mode="Markdown" > /dev/null 2>&1 &
fi
.claude/settings.json:
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "bash .claude/hooks/notify.sh"
}
]
}
]
}
}
Wszystkie zapytania sieciowe — w tle
Każdy curl w skrypcie musi mieć & na końcu. Jeśli sieć zwolni albo nastąpi timeout, zablokuje to całą sesję Claude'a. Wzorzec > /dev/null 2>&1 & to standard.
Stop nie zawsze znaczy „ukończone pomyślnie"
stop_reason o wartości end_turn zazwyczaj oznacza, że zadanie zostało wykonane, ale czasem Claude po prostu nie wie, co robić dalej. Jeśli chcesz dostawać powiadomienia tylko przy rzeczywistym zakończeniu, dodaj filtr:
[[ "$reason" != "end_turn" ]] && exit 0
Przycinaj treść powiadomienia
Ostatnia wiadomość Claude'a może być bardzo długa (bloki kodu, duży output). Przed wysłaniem do Slacka lub Telegrama przytnij ją przez head -c 300, żeby nie dostawać ścian tekstu.
Zmienne środowiskowe — poza repozytorium
URL webhooka i tokeny botów nie powinny trafiać do kodu projektu. Trzymaj je w ~/.zshrc (lub ~/.bashrc) albo zarządzaj nimi przez direnv.
Po skonfigurowaniu twój sposób pracy się zmienia:
Koniec z wpatrywaniem się w terminal, koniec z ciągłym przełączaniem okien, żeby sprawdzić postęp. Claude robi swoje, ty robisz swoje, a jak skończy — sam ci powie.