你开着一个长任务让 Claude Code 跑,然后去干别的事。等你想起来回来看,发现它卡在一个权限确认上已经等了二十分钟。
这种事发生过几次之后,手机推送就显得很必要了。
Claude Code 自带推送的问题
Claude Code 有个内置的 Remote Control 功能,配好之后可以从手机 app 控制桌面 session,也支持把通知推到手机。单机单 session 的话用起来没问题。
但只要你有多台机器,就会遇到一个根本性的限制:
手机 app 里有个 “dispatch” 设置,决定命令发到哪台机器。推送通知的路由跟 dispatch 目标绑在一起——你的 dispatch 指向哪台机器,就只有那台机器的通知能到手机。如果你现在在 MacBook 上跑任务,但 dispatch 绑的是台式机,通知就直接消失了。
我的场景:家里台式机和 MacBook 来回换,dispatch 永远只能指向一台。另一台跑的任务就完全静默,根本不知道 Claude 卡在哪。
还有个场景问题:同时跑多个项目、多台机器的时候,即便推送到了,你也不知道是哪台机器的哪个项目在叫你。要逐个切回去看,还不如没有通知。
所以我的思路是:用 Bark hook 绕过 dispatch 绑定问题,同时在通知里带上项目名,彻底解决溯源。
绕过去:用 Bark 自己做
Bark 是一个 iOS 推送工具,开源,免费,API 极简:
GET https://api.day.app/<你的key>/<标题>/<正文>返回 {"code":200,"message":"success"} 就到了。
Claude Code 的 Notification hook 正好对得上——每次 Claude 需要你注意(权限请求、等待输入、任务结束)都会触发,并且把上下文以 JSON 形式从 stdin 传进来。
在 ~/.claude/settings.json 里加一个 hook 就行:
{ "hooks": { "Notification": [ { "matcher": "", "hooks": [ { "type": "command", "command": "/Users/you/.claude/scripts/bark-notify.sh" } ] } ] }}第一版:固定文案
最简单的写法:
curl -s 'https://api.day.app/<key>/Claude%20Code/Task%20completed' > /dev/null 2>&1 &能收到,但没什么意义。每次手机弹出来都是 “Task completed”,你完全不知道是哪个项目、Claude 在说什么。
让通知真正有用
hook stdin 里有两个字段很关键:
.cwd— 当前工作目录,取 basename 就是项目名.transcript_path— 当前 session 的 JSONL 记录文件路径
transcript 文件里存着完整的对话历史,格式是每行一个 JSON 对象。assistant 的消息长这样:
{ "type": "assistant", "message": { "role": "assistant", "content": [ { "type": "text", "text": "已复制到 ~/Downloads/空壳.txt。" } ] }}所以提取最后一条 assistant 文本只需要:
tail -n 500 "$transcript_path" \ | jq -rs '[.[] | select(.type == "assistant")] | last // empty | .message.content[]? | select(.type == "text") | .text' \ | head -c 200tail -n 500 避免读整个大文件,200 字符截断足够一条通知展示。
完整脚本
#!/usr/bin/env bashBARK_KEY="${BARK_KEY:-your_bark_key_here}"BARK_HOST="${BARK_HOST:-https://api.day.app}"ICON="${BARK_ICON:-https://claude.ai/favicon.ico}"MAX_BODY="${BARK_MAX_BODY:-200}"
data=$(cat)
proj=$(printf '%s' "$data" | jq -r '.cwd // "" | split("/") | last // empty' 2>/dev/null)[ -z "$proj" ] && proj="Claude"
tx=$(printf '%s' "$data" | jq -r '.transcript_path // ""' 2>/dev/null)body=""if [ -n "$tx" ] && [ -f "$tx" ]; then body=$(tail -n 500 "$tx" 2>/dev/null \ | jq -rs '[.[] | select(.type == "assistant")] | last // empty | .message.content[]? | select(.type == "text") | .text' 2>/dev/null \ | head -c "$MAX_BODY")fiif [ -z "$body" ]; then body=$(printf '%s' "$data" | jq -r '.message // "Notification"' 2>/dev/null) [ -z "$body" ] && body="Notification"fi
title_enc=$(printf '%s' "Claude Code · $proj" | jq -sRr @uri)body_enc=$(printf '%s' "$body" | jq -sRr @uri)
curl -s "${BARK_HOST}/${BARK_KEY}/${title_enc}/${body_enc}?icon=${ICON}" > /dev/null 2>&1 &exit 0几个设计细节:
URL encoding 用 jq 的 @uri,不用 Python 或 node,减少依赖。jq 在装了 Claude Code 的机器上基本都有。
exit 0 兜底,防止任何内部错误把 hook 的退出码传回 Claude Code(Notification hook 退出码非零会被记录为错误)。
后台 & 发送 curl,不阻塞 hook 返回。
多层 fallback:transcript 不存在 → 用 .message;.message 也没有 → 用字面量 “Notification”。
安装
# 1. 把脚本放到 ~/.claude/scripts/mkdir -p ~/.claude/scripts# 把上面的脚本内容存进去,替换 your_bark_key_here
chmod +x ~/.claude/scripts/bark-notify.sh
# 2. 在 ~/.claude/settings.json 的 hooks.Notification 里加上这个 command# (见上面的 JSON 配置)或者用 repo 里的一键安装:
BARK_KEY=你的key bash install.shBark key 在 app 首页点钥匙图标就能看到。
效果
通知现在长这样:
- 标题:
Claude Code · my-project - 正文:
刚才那条测试推送收到没?(Claude 最后说的话,前 200 字)
你一眼就知道是哪个项目在叫你,大概卡在了什么地方。比 “Task completed” 有用多了。
TIP如果你在用自建 Bark 服务器,设置
BARK_HOST环境变量就行,脚本会自动用它替换默认的api.day.app。
依赖
jq—brew install jqcurl— macOS 自带- Bark iOS app — App Store 免费下载