1148 字
6 分钟
给 Claude Code 接上 Bark:让手机真正知道 AI 在干什么

你开着一个长任务让 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 就行:

~/.claude/settings.json
{
"hooks": {
"Notification": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "/Users/you/.claude/scripts/bark-notify.sh"
}
]
}
]
}
}

第一版:固定文案#

最简单的写法:

Terminal window
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 文本只需要:

Terminal window
tail -n 500 "$transcript_path" \
| jq -rs '[.[] | select(.type == "assistant")] | last // empty | .message.content[]? | select(.type == "text") | .text' \
| head -c 200

tail -n 500 避免读整个大文件,200 字符截断足够一条通知展示。

完整脚本#

~/.claude/scripts/bark-notify.sh
#!/usr/bin/env bash
BARK_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")
fi
if [ -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”。

安装#

Terminal window
# 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 里的一键安装:

StevenLi-phoenix
/
claude-bark-notify
Waiting for api.github.com...
00K
0K
0K
Waiting...
Terminal window
BARK_KEY=你的key bash install.sh

Bark key 在 app 首页点钥匙图标就能看到。

效果#

通知现在长这样:

  • 标题Claude Code · my-project
  • 正文刚才那条测试推送收到没?(Claude 最后说的话,前 200 字)

你一眼就知道是哪个项目在叫你,大概卡在了什么地方。比 “Task completed” 有用多了。

TIP

如果你在用自建 Bark 服务器,设置 BARK_HOST 环境变量就行,脚本会自动用它替换默认的 api.day.app

依赖#

  • jqbrew install jq
  • curl — macOS 自带
  • Bark iOS app — App Store 免费下载
给 Claude Code 接上 Bark:让手机真正知道 AI 在干什么
https://blog.lishuyu.top/posts/claude-code-bark-hook/
作者
猫猫魔女
发布于
2026-04-28
许可协议
CC BY-NC-SA 4.0