---
title: "用 Hooks 把 Claude Code 通知发到手机"
description: "Claude Code 需要授权或任务完成时，手机立刻收到提醒。基于 Notification 和 Stop hook 加 Webhook，配置可直接复制。"
date: 2026-10-02
author: Nooc
authorAvatarLink: /images/avatars/nooc.webp
authorLink: https://nooc.me
tags:
  - Claude Code
  - AI 编程智能体
  - hooks
  - 手机通知
  - Webhook
  - 开发者效率
---

# Claude Code 手机通知：它需要你、或已经做完时，第一时间知道

Claude Code 有两种值得关注的停下方式：一是卡住了在等你（权限确认、提问），二是已经做完了。如果你没盯着终端，这两种情况看起来完全一样——什么都不会发生，会话就一直闲置，直到你回来。解决办法是用 hook：在 `settings.json` 里加几行配置，任何一种情况发生的瞬间，就把 Webhook 发到你的手机。

这篇指南给出可以直接复制的配置，说明不同场景该用哪个 hook 事件，并讲清让它稳定工作的细节：怎么读取 hook 的输入、shell 引号怎么处理、以及如何避免被通知淹没。

## 什么场景用哪个 hook

Claude Code 会在生命周期事件上运行 hook。其中两个正好覆盖这个需求（[hooks 参考文档](https://code.claude.com/docs/en/hooks)）：

| 你想知道…… | Hook 事件 | Matcher / 过滤 |
| --- | --- | --- |
| Claude 在等你做权限决定 | `Notification` | `permission_prompt` |
| Claude 已经闲置、在等你输入 | `Notification` | `idle_prompt` |
| Claude 回复完毕 | `Stop` | 无需设置 |

`Notification` hook 的 `matcher` 按通知类型过滤，所以你可以精确选择哪些通知会送到手机。输入以 JSON 形式从 stdin 传入，包含 `notification_type`、`message`、`cwd` 和 `session_id`。`Notification` hook 无法阻止或修改任何行为——它们只是观察者，而这正是这里需要的。

`Stop` 的输入包含 `last_assistant_message`，也就是 Claude 产出的最后一段文字，正好可以白送一行通知摘要。

## 你需要什么

- 手机上装好 Echobell（[App Store](https://apps.apple.com/app/apple-store/id6743597198?pt=128151925&ct=blog-claude-code-phone-notifications-zh&mt=8) / [Google Play](https://play.google.com/store/apps/details?id=one.echobell.echobellandroid)）
- 运行 Claude Code 的机器上有 `curl` 和 `jq`
- 两分钟

## 第 1 步 —— 建两个频道

别把所有通知都塞进同一个频道。在 Echobell 里创建：

- **Claude Blocked**，类型选 **时效性通知（Time Sensitive）** 或 **来电（Calling）** —— 用于权限确认：智能体已经停摆，每过一分钟都是在白白浪费算力和时间
- **Claude Done**，类型选 **普通（Normal）** —— 一条普通推送，什么时候看都行

复制每个频道的 Webhook URL，形如 `https://hook.echobell.one/t/<channel-token>`。把它当密码对待：任何拿到它的人都能让你的手机响铃。

把两个 URL 存成环境变量，这样它们就不会进到提交的配置文件里：

```bash
export ECHOBELL_CLAUDE_BLOCKED_URL="https://hook.echobell.one/t/<blocked-token>"
export ECHOBELL_CLAUDE_DONE_URL="https://hook.echobell.one/t/<done-token>"
```

把这两行放进 shell 配置文件，Claude Code 才能继承到它们。

## 第 2 步 —— 添加 hooks

要对所有项目生效，加到 `~/.claude/settings.json`；只对某个仓库生效，加到 `.claude/settings.local.json`：

```json
{
  "hooks": {
    "Notification": [
      {
        "matcher": "permission_prompt",
        "hooks": [
          {
            "type": "command",
            "command": "jq -c '{title: \"Claude 需要授权\", body: .message, project: (.cwd | split(\"/\") | last)}' | curl -sS -m 10 -X POST -H 'Content-Type: application/json' -d @- \"$ECHOBELL_CLAUDE_BLOCKED_URL\""
          }
        ]
      }
    ],
    "Stop": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "jq -c '{title: \"Claude 已完成\", body: (.last_assistant_message // \"完成\" | .[0:200]), project: (.cwd | split(\"/\") | last)}' | curl -sS -m 10 -X POST -H 'Content-Type: application/json' -d @- \"$ECHOBELL_CLAUDE_DONE_URL\""
          }
        ]
      }
    ]
  }
}
```

各部分的作用：

- `jq -c '{...}'` 从 stdin 读取 hook 的 JSON 并构造一个小载荷，这样 Echobell 收到的正是你想要的字段，而不是整份 hook 文档。
- `.cwd | split("/") | last` 把 `/home/me/code/api` 变成 `api`，通知里就能看出是哪个项目在请求你。
- `.[0:200]` 截断最后一条消息。Echobell worker 把通知正文限制在 1500 字节以内，而锁屏上能显示的本来就少得多。
- `-m 10` 给 `curl` 设了 10 秒超时，网络出问题时 Claude Code 也不会一直等你的 hook。

Echobell 通过频道模板渲染你发送的键，而新建的频道没有读取这些键的模板——请在频道设置里把标题模板设为 `{{title}}`、正文模板设为 `{{body}}`。想显示项目名的话，在其中一个里加上 `{{project}}`，例如 `{{title}} ({{project}})`。

## 第 3 步 —— 不等真实提示，直接测试

用一份伪造的 hook 载荷，手动跑一遍同样的管道：

```bash
echo '{"message":"Claude needs your permission to use Bash","cwd":"/home/me/code/api","notification_type":"permission_prompt"}' \
  | jq -c '{title: "Claude 需要授权", body: .message, project: (.cwd | split("/") | last)}' \
  | curl -sS -m 10 -X POST -H 'Content-Type: application/json' -d @- "$ECHOBELL_CLAUDE_BLOCKED_URL"
```

如果手机亮了，说明 Echobell 这一侧没问题，剩下的问题都出在 Claude Code 读取配置的方式上。在 Claude Code 里运行 `/hooks` 确认 hook 已加载；会话运行期间修改的设置，可能需要通过 hooks 菜单或重启才能生效。

## 第 4 步 —— 让「被阻塞」频道真正响铃

推送通知打不过勿扰模式。**来电（Calling）** 频道的表现和来电一样，能穿透 iOS 专注模式——当你启动一个长任务、晚上出门时，这一点最重要。如果你只想在部分提示出现时被打扰，不要去改 hook，而是在 Echobell 频道里对你发送的变量设置条件，例如 `project == "api"`。完整的变量列表见[条件参考](/zh/docs/conditions)。

## 避免通知过载

一个繁忙的会话可能触发大量 `Stop` 事件——每次回复结束都会有一个。保住有效信号有三个办法：

- **不要把 `Stop` 发到会响铃的频道。** 任务完成并不紧急，保持普通推送即可。
- **用 `idle_prompt` 代替 `Stop`**，如果你只想在 Claude 停下来等你时才收到通知，而不是每次回复之后都收到。
- **按项目收窄**，用条件过滤，这样临时的试验仓库就完全不会通知你。

## 在远程环境或 CI 中运行 Claude Code

同样的方法在云端或远程环境里也能用，因为 hook 只是一个对外的 HTTPS 请求，不需要任何东西能被公网访问。把这两个环境变量配置到环境的 secrets 里，而不是 shell 配置文件里，无论会话跑在哪里，通知都能送到你的手机。

## 故障排查

**什么都没收到，手动测试也失败。** 检查启动 Claude Code 的那个 shell 里是否设置了 URL 变量：`echo "$ECHOBELL_CLAUDE_BLOCKED_URL"`。变量为空时 `curl` 会静默失败。

**手动测试能通，但 hook 不触发。** 确认 matcher：`permission_prompt` 和 `idle_prompt` 是不同的，不匹配的 matcher 只是永远不会运行。运行 `/hooks` 并检查 JSON 是否合法——多一个尾随逗号，就会让整个 settings 文件里的 hook 全部失效。

**通知正文是空的。** `jq` 过滤器引用了该事件中不存在的字段。`message` 存在于 `Notification` 的输入中；`last_assistant_message` 存在于 `Stop` 的输入中。像上面的配置那样，用 `//` 提供默认值。

**触发太频繁。** 参见上一节——把 `Stop` 换成 `idle_prompt`，或者加一个条件。

## 常见问题

### Claude Code 需要授权时，怎么收到通知？

添加一个 matcher 为 `permission_prompt` 的 `Notification` hook，把 Webhook 发到某个频道。Echobell 会把这个请求变成手机上的推送或来电式提醒。

### 怎么知道 Claude Code 什么时候完成了任务？

添加一个 `Stop` hook。它在 Claude 回复完毕时运行，输入里包含 `last_assistant_message`，可以直接用作通知正文。

### 通知能突破勿扰模式吗？

可以，前提是接收的频道是来电（Calling）频道。它的表现就像来电，iOS 专注模式会放行；而任何 App 的普通推送都不行。

### 在网页版 Claude Code 或远程会话里能用吗？

能。hook 只发起一个对外的 HTTPS 请求，所以只要会话能访问互联网就能用。通过环境的 secrets 提供频道 URL 即可。

### 把 Webhook URL 写进 settings.json 安全吗？

建议像上面那样用环境变量，这样令牌就不会被提交进仓库。如果泄露了，新建一个频道，停用旧的。

## 小结

两个频道、两个 hook，加一条 `jq | curl` 管道：`permission_prompt` 走**来电（Calling）**或时效性频道，`Stop` 走普通频道。之后你就可以启动一个长时间的智能体任务，离开房间，只在智能体真正需要你的时候被叫回来。

[下载 iPhone 版 Echobell](https://apps.apple.com/app/apple-store/id6743597198?pt=128151925&ct=blog-claude-code-phone-notifications-zh&mt=8)，或[前往 Google Play 获取](https://play.google.com/store/apps/details?id=one.echobell.echobellandroid)，然后先跑一遍上面的手动测试，再放心地把长任务交给它。

## 相关阅读

- [把智能体的审批关卡变成电话](/zh/blog/ai-agent-human-in-the-loop-alerts)
- [长时间的终端命令跑完时收到通知](/zh/blog/notify-when-terminal-command-finishes)
- [Webhook 集成指南](/zh/docs/webhook)
- [频道条件参考](/zh/docs/conditions)
- [用关键提醒绕过 iOS 专注模式](/zh/blog/how-to-bypass-ios-focus-mode-for-critical-alerts)
