配置 Stop Hook,让 Claude 完成任务后通过桌面通知、Slack、Telegram 自动推送消息
Claude Code 执行任务经常需要几分钟甚至更长。你让它重构一个模块、跑完测试套件、处理一批文件——然后切窗口去做别的事。问题是:你不知道它什么时候干完了。
回来一看,可能已经完成了十分钟,也可能卡在一个确认弹窗上等你。
用 Stop Hook 可以在 Claude 完成任务(或需要你介入)时自动发通知。不管你在哪个窗口、哪台设备,通知直接推过来。
Claude Code 有一个 Stop 事件——每次 Claude 停下来时触发,不管是任务完成、遇到错误需要确认,还是等待用户输入。
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "bash .claude/hooks/notify.sh"
}
]
}
]
}
}
Hook 通过 stdin 拿到一个 JSON,里面包含 stop_reason(停止原因)和 stop_message(Claude 最后说的话)。根据这些信息,你可以定制通知内容。
stdin 数据结构:
{
"session_id": "xxx",
"stop_reason": "end_turn",
"stop_message": "重构完成,所有测试通过。"
}
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
优点:零配置,即开即用。
缺点:只有当前机器能收到,切到其他设备就看不到了。
用 Slack Incoming Webhook 把通知推到指定频道或私信。
前置准备:
1. 在 Slack 的 App 管理页面 创建一个 App
2. 启用 Incoming Webhooks,拿到 Webhook 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。
Telegram Bot 适合个人使用,免费、即时,手机上就能收到。
前置准备:
1. 找 @BotFather 创建 Bot,拿到 Token
2. 获取你的 Chat ID(给 Bot 发条消息,然后调用 getUpdates API)
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 &
如果你在同一台机器上但切了窗口,一声提示音比弹窗更有效:
#!/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 和 Bot Token 不应该写进项目仓库。建议放在 ~/.zshrc(或 ~/.bashrc)里,或者用 direnv 管理。
配置之后,你的工作方式变了:
不用盯着终端等,不用反复切窗口看进度。Claude 干活,你干你的,完事通知你。