Configure um Stop Hook para o Claude avisar automaticamente via desktop, Slack ou Telegram ao terminar uma tarefa
Tarefas no Claude Code costumam levar alguns minutos ou mais. Você pede pra ele refatorar um módulo, rodar a suite de testes, processar um lote de arquivos — e vai fazer outra coisa. O problema é que você não sabe quando ele terminou.
Quando você volta pra olhar, talvez já tenha acabado há dez minutos. Ou talvez esteja preso num prompt de confirmação esperando por você.
Com o Hook Stop, você recebe uma notificação automática sempre que o Claude terminar uma tarefa ou precisar da sua intervenção. Não importa em qual janela ou dispositivo você esteja — a notificação chega direto.
O Claude Code tem um evento Stop que é disparado toda vez que o Claude para, seja porque completou a tarefa, encontrou um erro que precisa de confirmação, ou está esperando input do usuário.
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "bash .claude/hooks/notify.sh"
}
]
}
]
}
}
O hook recebe via stdin um JSON contendo stop_reason (o motivo da parada) e stop_message (a última coisa que o Claude disse). Com essas informações, você pode personalizar o conteúdo da notificação.
Estrutura do JSON de entrada:
{
"session_id": "xxx",
"stop_reason": "end_turn",
"stop_message": "重构完成,所有测试通过。"
}
No Linux usa notify-send, no macOS usa osascript. Não precisa de nenhum serviço externo:
#!/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
Vantagem: zero configuração, funciona de cara.
Desvantagem: só funciona na máquina local. Se você trocar de dispositivo, não vai ver.
Use um Incoming Webhook do Slack para enviar notificações para um canal ou mensagem direta.
Pré-requisitos:
1. Crie um App na página de gerenciamento do Slack
2. Ative Incoming Webhooks e copie a URL do webhook
3. Salve a URL na variável de ambiente 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 &
O & no final é essencial — faz o curl rodar em segundo plano sem bloquear o Claude.
Um Bot do Telegram é perfeito para uso pessoal: gratuito, instantâneo, e a notificação chega direto no celular.
Pré-requisitos:
1. Crie um Bot com o @BotFather e pegue o token
2. Descubra seu Chat ID (mande uma mensagem pro Bot e consulte a API getUpdates)
3. Configure as variáveis de ambiente TELEGRAM_BOT_TOKEN e 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 &
Se você está na mesma máquina mas trocou de janela, um som pode ser mais eficiente que um popup:
#!/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
Dá pra combinar com as outras opções — som local + notificação remota.
Na prática, o ideal é combinar vários métodos num único script:
.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"
}
]
}
]
}
}
Todas as requisições de rede devem rodar em segundo plano
Os curl no script de notificação sempre precisam do &. Se a rede estiver lenta ou der timeout, trava a sessão inteira do Claude. Usar > /dev/null 2>&1 & é o padrão.
Stop não significa "concluído com sucesso"
Um stop_reason igual a end_turn geralmente indica que a tarefa terminou, mas também pode significar que o Claude não sabe o que fazer em seguida. Se você só quer ser notificado quando realmente terminou, filtre no script:
[[ "$reason" != "end_turn" ]] && exit 0
Truncar o conteúdo da notificação
A última mensagem do Claude pode ser bem longa (com blocos de código, saídas extensas). Antes de mandar pro Slack ou Telegram, use head -c 300 pra truncar e evitar mensagens gigantes.
Variáveis de ambiente devem ficar na configuração global
URLs de webhooks e tokens de bots não devem ficar no repositório do projeto. O ideal é colocar no ~/.zshrc (ou ~/.bashrc), ou gerenciar com direnv.
Depois de configurar, seu jeito de trabalhar muda:
Sem ficar olhando pro terminal, sem ficar alternando entre janelas pra ver se acabou. O Claude trabalha, você trabalha, e quando ele terminar, te avisa.