Сповіщення через хуки: Claude сам повідомить, коли закінчив

Налаштуйте Stop Hook, щоб Claude автоматично сповіщав вас через робочий стіл, Slack або Telegram після завершення завдання


Claude Code часто працює кілька хвилин, а то й довше. Ви просите його зробити рефакторинг модуля, прогнати тести, обробити пачку файлів — і перемикаєтесь на інші справи. Проблема: ви не знаєте, коли він закінчить.

Повертаєтесь — а він, можливо, вже десять хвилин як усе зробив. Або застряг на вікні підтвердження і чекає на вас.

За допомогою хука Stop можна автоматично отримувати сповіщення, щойно Claude завершив задачу або потребує вашого втручання. Неважливо, в якому ви вікні чи на якому пристрої — сповіщення дійде.


Як це працює

У Claude Code є подія Stop — вона спрацьовує щоразу, коли Claude зупиняється: завершив задачу, натрапив на помилку або чекає на введення від користувача.

{
  "hooks": {
    "Stop": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "bash .claude/hooks/notify.sh"
          }
        ]
      }
    ]
  }
}

Хук отримує на stdin JSON із полями stop_reason (причина зупинки) та stop_message (останнє повідомлення Claude). На основі цих даних можна налаштувати зміст сповіщення.

Структура JSON на stdin:

{
  "session_id": "xxx",
  "stop_reason": "end_turn",
  "stop_message": "重构完成,所有测试通过。"
}

Варіант 1: системні сповіщення на робочому столі (найпростіший)

На Linux — notify-send, на macOS — osascript. Жодних зовнішніх сервісів:

#!/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

Плюс: нульова конфігурація, працює одразу.
Мінус: сповіщення приходить тільки на поточну машину — на іншому пристрої ви його не побачите.


Варіант 2: Slack Webhook (рекомендовано для команд)

Через Slack Incoming Webhook можна надсилати сповіщення в потрібний канал або в особисті повідомлення.

Підготовка:
1. Створіть додаток на сторінці керування App у Slack
2. Увімкніть Incoming Webhooks і скопіюйте URL вебхука
3. Збережіть URL у змінній оточення 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 &

& наприкінці важливий — curl виконується у фоні й не блокує Claude.


Варіант 3: Telegram-бот

Telegram-бот ідеально підходить для особистого використання — безкоштовно, миттєво, сповіщення приходять прямо на телефон.

Підготовка:
1. Створіть бота через @BotFather і отримайте токен
2. Дізнайтеся свій Chat ID (надішліть боту повідомлення, потім викличте API getUpdates)
3. Задайте змінні оточення TELEGRAM_BOT_TOKEN та 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 &

Варіант 4: звуковий сигнал (зручно і через SSH)

Якщо ви на тій самій машині, але перемкнулися в інше вікно, звук часто ефективніший за спливаюче сповіщення:

#!/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

Можна комбінувати з іншими варіантами — локальний звук плюс віддалений пуш.


Повна конфігурація: поєднуємо кілька способів

На практиці найкраще об'єднати все в одному скрипті:

.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"
          }
        ]
      }
    ]
  }
}

Кілька зауважень

Усі мережеві запити — у фон

Усі curl у скрипті обов'язково запускайте з &. Якщо мережа гальмує або трапляється таймаут, це заблокує всю сесію Claude. Конструкція > /dev/null 2>&1 & — стандартна практика.

Stop — не завжди «успішно завершено»

stop_reason зі значенням end_turn зазвичай означає, що задачу виконано, але інколи Claude просто не знає, що робити далі. Якщо хочете отримувати сповіщення лише при реальному завершенні, додайте фільтр:

[[ "$reason" != "end_turn" ]] && exit 0

Обрізайте текст сповіщення

Останнє повідомлення Claude може бути дуже довгим (блоки коду, великий вивід). Перед відправкою в Slack або Telegram обріжте його через head -c 300, щоб не отримувати простирадла тексту.

Змінні оточення — поза репозиторієм

Webhook URL і токени ботів не повинні потрапляти в код проєкту. Зберігайте їх у ~/.zshrc (або ~/.bashrc) або використовуйте direnv.


Результат

Після налаштування ваш робочий процес змінюється:

  1. Даєте Claude задачу
  2. Перемикаєтесь на інше вікно або пристрій
  3. Claude закінчує → приходить сповіщення
  4. Повертаєтесь, дивитесь результат, продовжуєте

Не треба сидіти й витріщатися в термінал, не треба постійно перемикатися, щоб перевірити прогрес. Claude працює, ви займаєтесь своїми справами, а коли він закінчить — сам повідомить.