Dùng Hooks để nhận thông báo khi Claude hoàn thành công việc

Cấu hình Stop Hook để Claude tự động gửi thông báo qua desktop, Slack hoặc Telegram khi hoàn thành công việc


Claude Code thường mất vài phút hoặc lâu hơn để hoàn thành một tác vụ. Bạn bảo nó refactor một module, chạy bộ test, xử lý hàng loạt file — rồi chuyển sang cửa sổ khác làm việc khác. Vấn đề là: bạn không biết khi nào nó xong.

Quay lại kiểm tra thì có khi đã xong từ mười phút trước, hoặc đang treo ở một popup xác nhận chờ bạn.

Với Stop Hook, bạn có thể nhận thông báo tự động mỗi khi Claude hoàn thành tác vụ (hoặc cần bạn can thiệp). Dù bạn đang ở cửa sổ nào, thiết bị nào, thông báo cũng được đẩy tới ngay.


Cơ chế hoạt động

Claude Code có một sự kiện Stop — được kích hoạt mỗi khi Claude dừng lại, dù là hoàn thành tác vụ, gặp lỗi cần xác nhận, hay đang chờ người dùng nhập liệu.

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

Hook nhận một JSON qua stdin, chứa stop_reason (lý do dừng) và stop_message (câu cuối cùng Claude nói). Dựa vào đó, bạn có thể tuỳ chỉnh nội dung thông báo.

Cấu trúc dữ liệu stdin:

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

Cách 1: Thông báo desktop hệ thống (Đơn giản nhất)

Linux dùng notify-send, macOS dùng osascript, không cần bất kỳ dịch vụ bên ngoài nào:

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

Ưu điểm: Không cần cấu hình gì, dùng ngay được.
Nhược điểm: Chỉ nhận được trên máy hiện tại — chuyển sang thiết bị khác thì không thấy.


Cách 2: Slack Webhook (Khuyên dùng cho team)

Dùng Slack Incoming Webhook để đẩy thông báo vào channel hoặc tin nhắn riêng.

Chuẩn bị:
1. Tạo App trên trang quản lý App của Slack
2. Bật Incoming Webhooks và lấy Webhook URL
3. Lưu URL vào biến môi trường 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 &

Dấu & ở cuối rất quan trọng — để curl chạy nền, không chặn Claude.


Cách 3: Telegram Bot

Telegram Bot phù hợp cho cá nhân — miễn phí, tức thời, nhận thông báo ngay trên điện thoại.

Chuẩn bị:
1. Tạo Bot qua @BotFather và lấy Token
2. Lấy Chat ID của bạn (gửi tin nhắn cho Bot, rồi gọi API getUpdates)
3. Đặt biến môi trường TELEGRAM_BOT_TOKENTELEGRAM_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 &

Cách 4: Thông báo bằng âm thanh (Hữu ích cả khi SSH)

Nếu bạn vẫn ở trên cùng một máy nhưng đang ở cửa sổ khác, một tiếng "ding" hiệu quả hơn popup nhiều:

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

Có thể kết hợp với các cách khác — phát âm thanh ở máy cục bộ + đẩy thông báo ra thiết bị khác.


Cấu hình đầy đủ: Kết hợp nhiều cách thông báo

Trong thực tế, nên gộp nhiều cách vào một script duy nhất:

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

Một số lưu ý

Mọi request mạng đều phải chạy nền

Các lệnh curl trong script thông báo bắt buộc phải thêm & để chạy nền. Nếu mạng chậm hoặc timeout, nó sẽ chặn toàn bộ phiên làm việc của Claude. Thêm > /dev/null 2>&1 & là cách làm chuẩn.

Stop không có nghĩa là "hoàn thành thành công"

stop_reasonend_turn thường có nghĩa tác vụ đã xong, nhưng cũng có thể Claude đang không chắc bước tiếp theo là gì. Nếu bạn chỉ muốn nhận thông báo khi thực sự hoàn thành, thêm bộ lọc vào script:

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

Cắt ngắn nội dung thông báo

Tin nhắn cuối của Claude có thể rất dài (chứa code block, output dài). Trước khi gửi lên Slack hay Telegram, dùng head -c 300 để cắt ngắn, tránh thông báo bị "nổ".

Lưu biến môi trường ở cấu hình toàn cục

Webhook URL và Bot Token không nên lưu trong repository dự án. Nên đặt trong ~/.zshrc (hoặc ~/.bashrc), hoặc quản lý bằng direnv.


Kết quả

Sau khi cấu hình xong, cách làm việc của bạn thay đổi:

  1. Giao cho Claude một tác vụ
  2. Chuyển sang cửa sổ/thiết bị khác
  3. Claude xong → thông báo đẩy tới ngay
  4. Quay lại xem kết quả, tiếp tục bước tiếp theo

Không cần dán mắt vào terminal, không cần liên tục chuyển cửa sổ kiểm tra tiến độ. Claude làm việc của Claude, bạn làm việc của bạn — xong thì nó báo.