Claude Code 任务完成通知实战:SSH 远程服务器与本地环境接入飞书机器人
Claude Code 任务完成通知实战:SSH 远程服务器与本地环境接入飞书机器人
最近在使用 Claude Code 时,我遇到了一个非常实际的问题:
Claude Code 很适合处理多步任务,但无论是在 SSH 远程服务器上运行,还是在本地终端中运行,只要任务稍微长一点,就不得不一直盯着终端窗口,等待它结束、等待它请求授权,或者等待它回到需要我输入下一步指令的状态。
这显然不够高效。
于是,我开始折腾一套“Claude Code → Hook → Python 脚本 → 飞书机器人”的通知链路,希望在下面几种场景中自动收到消息:
- Claude 完成一轮响应;
- Claude 做完当前工作,开始等待我下一步输入;
- Claude 需要我授权;
- Claude 主动向我发起追问或确认。
整个过程看上去只是“发个 webhook”这么简单,但真正做下来,踩坑并不少:
- 一开始误以为只要配置
Notification,Claude 每次回复结束后都会通知; - 飞书 webhook 地址曾经写错,前缀重复导致直接
404; - SSH 场景下服务器开了 Clash 代理,hook 脚本偶发报
Connection reset by peer; - Windows 本地路径因为反斜杠转义,被 shell 解析坏掉;
- 本地 hook 执行时还遇到了
utf-8 codec can't encode surrogate这类编码问题。
最后,我把两套环境都完全跑通了:
- SSH 远程 Linux 服务器运行 Claude Code → 飞书通知
- 本地 Windows 直接运行 Claude Code → 飞书通知
这篇文章把整个过程完整整理一遍,尽量写成一份可以直接照着配置的实战记录。
一、先说结论:Claude Code 通知到底该怎么理解
Claude Code 的通知核心并不是终端弹窗,而是 hooks。
在这次实践里,最重要的是两个事件:
1. Stop
Stop 表示:
Claude 完成一轮响应时触发。
它更适合“我给 Claude 发了一条指令,Claude 回答完以后就通知我”的场景。
如果你的目标是:
- 我发一句话;
- Claude 回复结束;
- 然后飞书提醒我;
那么核心应该配的是 Stop。
2. Notification
Notification 并不是“每次回复都触发”,而是:
Claude 进入需要你注意的状态时触发。
常见包括:
idle_prompt:当前工作完成,开始等待你下一步输入;permission_prompt:需要你授权;elicitation_dialog:Claude 主动向你提问;auth_success:认证相关事件完成。
也就是说,如果你只配置 Notification,然后输入一句简单的 hello,Claude 回复结束后通常不会立即通知。因为它不等于“回复完成”,它更像“现在需要你注意”。
3. 最推荐的配置方式
如果希望通知尽量完整,我建议:
Stop:负责“每轮响应结束”;Notification:负责“需要你注意”。
如果你希望飞书安静一点,可以只保留 Stop;如果你只关心“真正需要我介入”的时刻,可以只保留 Notification。
二、整体方案:Claude Code → Hook → 飞书机器人
我最后采用的整体方案非常简单:
graph LR
Claude[Claude Code] --> Hook[Hook 触发]
Hook --> Python[notify_feishu.py]
Python --> Feishu[飞书机器人 Webhook]
Feishu --> User[收到通知]
也就是说:
- Claude Code 在指定时机触发 hook;
- hook 调用本地命令;
- 本地命令执行 Python 脚本;
- Python 脚本向飞书机器人发送文本消息;
- 飞书群收到提醒。
这个思路在 SSH 远程 Linux 和 本地 Windows 上都成立,只是路径、环境变量和代理处理略有区别。
三、飞书机器人准备工作
无论你是在远程服务器上配,还是在本地机器上配,飞书机器人都要先准备好。
1. 在飞书群中添加自定义机器人
进入你的飞书群,添加一个 自定义机器人,然后记录两项信息:
WebhookSecret(如果你启用了签名校验)
最终拿到的 webhook 地址通常是这种格式:
1 | https://open.feishu.cn/open-apis/bot/v2/hook/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx |
2. 一个非常容易踩的坑:Webhook 前缀重复
我一开始把环境变量写成了下面这样:
1 | export FEISHU_WEBHOOK='https://open.feishu.cn/open-apis/bot/v2/hook/https://open.feishu.cn/open-apis/bot/v2/hook/da4fdb04-0be6-4cff-972c-da029f870919' |
这会直接导致:
1 | HTTP Error 404: Not Found |
因为 webhook 的前缀被写了两遍。
正确写法应该是:
1 | export FEISHU_WEBHOOK='https://open.feishu.cn/open-apis/bot/v2/hook/da4fdb04-0be6-4cff-972c-da029f870919' |
因此,如果你一开始就收到 404,最先检查的不是 Claude 配置,也不是 Python 逻辑,而是:
Webhook 地址是否写对。
四、SSH 远程服务器场景:Linux 上让 Claude Code 自动发飞书通知
这是最有价值的场景之一,因为真正需要通知的,往往就是挂在服务器上跑的任务。
典型使用方式如下:
- 本地通过 SSH 连接远程 Linux;
- 在远程服务器上启动 Claude Code;
- Claude 做长任务时不再盯着终端;
- 通过飞书获取任务完成、等待输入、权限请求等提醒。
1. 推荐目录结构
我最后统一放在下面这些路径中:
1 | ~/.claude/ |
创建目录:
1 | mkdir -p ~/.claude/hooks |
2. 在远程 Linux 上配置飞书环境变量
我把飞书 webhook 和 secret 写入当前用户的 ~/.bashrc:
1 | export FEISHU_WEBHOOK='https://open.feishu.cn/open-apis/bot/v2/hook/你的token' |
然后执行:
1 | source ~/.bashrc |
检查是否生效:
1 | python3 - <<'PY' |
如果输出里能看到正确的值,说明环境变量已经就绪。
3. 先单独测试脚本链路,而不是一上来就测 Claude
这一步非常重要。
在我整个排查过程中,最大的经验就是:
先把“Python 脚本 → 飞书”这条链路单独打通,再去接 Claude Code。
测试方法如下:
1 | echo '{ |
如果飞书群能收到消息,说明以下环节都正常:
- webhook 正确;
- secret 正确;
- Python 脚本可执行;
- 服务器到飞书的网络链路正常。
只有这一条先通了,后面出了问题你才能明确知道是 Claude hook 配置层的问题,而不是脚本本身的问题。
4. Linux 下最终可用的飞书通知脚本
我最后整理出的版本解决了几个关键问题:
- 支持
Stop和Notification; - 自动写日志;
- 对网络错误自动重试;
- 能处理奇怪字符,避免编码报错;
- 最关键:显式禁用
urllib自动继承系统代理。
这份脚本保存为:
1 | ~/.claude/hooks/notify_feishu.py |
内容如下:
1 | #!/usr/bin/env python3 |
赋予执行权限:
1 | chmod +x ~/.claude/hooks/notify_feishu.py |
5. 远程 Linux 下的 settings.json
我最后推荐使用下面这份最小可用配置:
1 | { |
它的含义是:
Stop:Claude 一轮回答结束后执行脚本;Notification:Claude 需要你注意时执行脚本。
如果你嫌飞书太频繁,可以只保留 Stop。
6. 我在远程 Linux 上遇到的代理问题
这是整个过程中最隐蔽、也最容易误判的问题之一。
我当时在服务器上开了 Clash,环境变量大致如下:
1 | HTTPS_PROXY=http://127.0.0.1:7890 |
随后出现了一个非常迷惑的现象:
- 手工执行 Python 脚本,有时成功;
- Claude Code 触发 hook 时,偶发报错:
1 | Connection reset by peer |
最后排查出来,问题并不在 Claude,也不在飞书,而在于:
Python 的
urllib默认会自动继承环境变量中的代理设置。
也就是说:
- 你的通知脚本实际上在走 Clash 代理;
- 这条代理链路偶发不稳定;
- 所以 hook 在发送 webhook 的时候会随机失败。
最稳的处理方式
直接在脚本中禁用代理继承:
1 | opener = request.build_opener(request.ProxyHandler({})) |
这样,脚本只在“飞书通知”这个动作上直连外网,不影响你其他终端命令继续走代理。
这是我最终认为最干净、最稳定的解决方式。
7. 如何判断远程 Linux 上的 hook 已经真的生效
在 Claude Code 中输入一句简单的话,例如:
1 | hello |
如果 Stop hook 配置正确,你通常会看到类似:
1 | Ran 1 stop hook |
如果飞书没有收到消息,不要先怀疑 Claude 配置没读到,而应该先检查日志:
1 | tail -n 50 ~/.claude/hooks/notify_feishu.log |
如果日志中有事件记录,就说明:
- hook 的确触发了;
- 问题在 Python 脚本或网络;
- 不是
settings.json没加载。
五、本地 Windows 场景:直接运行 Claude Code 并接入飞书通知
远程 Linux 跑通之后,我又把本地 Windows 环境也完整配了一遍。
这个场景其实更舒服,因为:
- 不需要 SSH;
- 不需要担心服务器链路;
- Claude Code 可以直接在本地终端运行;
- VS Code 中的 Claude Code 也能共用同一份配置。
1. 本地目录结构
我最后统一采用下面这种结构:
1 | C:\Users\你的用户名\.claude\ |
创建目录:
1 | New-Item -ItemType Directory -Force "$env:USERPROFILE\.claude\hooks" | Out-Null |
2. 在 Windows 上配置飞书环境变量
我使用的是用户级环境变量:
1 | [Environment]::SetEnvironmentVariable('FEISHU_WEBHOOK', 'https://open.feishu.cn/open-apis/bot/v2/hook/你的token', 'User') |
为了让当前 PowerShell 会话立刻可用,再补两句:
1 | $env:FEISHU_WEBHOOK = [Environment]::GetEnvironmentVariable('FEISHU_WEBHOOK', 'User') |
检查是否生效:
1 | echo $env:FEISHU_WEBHOOK |
3. Windows 下的脚本其实可以直接复用 Linux 那一版
本地 Windows 场景中,核心脚本逻辑并不需要大改。
也就是说,只要把 Linux 中那份 notify_feishu.py 放到:
1 | C:\Users\pc\.claude\hooks\notify_feishu.py |
通常就可以直接使用。
真正需要注意的,主要是 路径写法 和 奇怪字符导致的编码问题。
4. Windows 下最容易踩的坑:路径被 shell 解析坏了
我最初在 settings.json 中写的是:
1 | "command": "py -3 C:\\Users\\pc\\.claude\\hooks\\notify_feishu.py" |
结果 Claude 执行 hook 时直接报错,路径被解析成了乱七八糟的形式,例如:
1 | C:\Users\pc\Userspc.claudehooksnotify_feishu.py |
本质原因是:
某些 shell 在解析命令字符串时,会把反斜杠当作转义字符。
解决方法
最稳妥的方式有两种:
方法一:给完整路径加引号
1 | "command": "py -3 \"C:\\Users\\pc\\.claude\\hooks\\notify_feishu.py\"" |
方法二:直接使用正斜杠
1 | "command": "py -3 \"C:/Users/pc/.claude/hooks/notify_feishu.py\"" |
我最后更推荐第二种,因为它更简洁,也更不容易再出现转义问题。
5. Windows 本地推荐的 settings.json
这是我最终保留下来的本地版本:
1 | { |
只需要把其中的用户名路径替换成你自己的即可。
6. Windows 本地的另一个坑:UTF-8 surrogate 编码报错
当路径问题解决之后,我又遇到了一个更隐蔽的错误:
1 | 'utf-8' codec can't encode character '\udc80' ... surrogates not allowed |
这类错误的根本原因是:
Hook 输入里混入了某些不能直接按 UTF-8 写出的字符,而脚本又把它们原样写到日志或原样拼接进了文本。
我的解决方法
在脚本中增加一个安全转换函数:
1 | def safe_text(x): |
然后:
- 日志写文件使用
errors="backslashreplace"; - 发给飞书的文本也先经过
safe_text(); json.dumps(..., ensure_ascii=True)保证特殊字符被安全转义。
这样就能把这类本来会导致脚本崩溃的字符,变成可安全写出的转义文本。
7. Windows 本地的测试方式
第一步:先单独测脚本
1 | '{ |
如果飞书群收到消息,说明:
- Python 环境正常;
- webhook 和 secret 正确;
- 脚本可以独立跑通。
第二步:再测 Claude Code
重新打开一个终端,启动:
1 | claude |
然后输入:
1 | hello |
如果 Stop hook 配对,Claude 回复结束后就会触发飞书通知。
六、VS Code 中的 Claude Code 是否也能复用这套通知配置
可以,而且这是我认为这套方案很舒服的一点。
本地环境中:
Claude Code CLI 和 VS Code 中的 Claude Code 扩展,默认共用同一份
~/.claude/settings.json配置。
这意味着:
- 你在本地终端里跑 Claude Code,飞书通知会触发;
- 你在 VS Code 中使用 Claude Code,同样也能触发同样的 hook;
- 你不需要额外为 VS Code 单独维护一份通知逻辑。
因此,本地只需要维护一份:
notify_feishu.py~/.claude/settings.json
就足够了。
七、我最后的推荐配置思路
1. 远程 Linux
适合:
- SSH 跑长任务;
- 不想一直盯终端;
- 希望任务完成、等待输入或权限请求时飞书提醒。
推荐:
- 配置
Stop+Notification; - Python 脚本中显式禁用代理继承;
- 开启日志;
- 增加简单重试。
2. 本地 Windows
适合:
- 本地终端运行 Claude Code;
- 本地 VS Code 扩展一起复用;
- 统一使用飞书通知。
推荐:
- 路径一定加引号;
- 最好使用正斜杠路径;
- 脚本中使用
safe_text()处理怪字符。
3. 如果你只想让通知最安静
只保留:
1 | "hooks": { |
这样每轮结束发一次,不会太吵。
4. 如果你想尽可能不错过任何状态
同时保留:
StopNotification
并让 Notification 匹配:
idle_promptpermission_promptelicitation_dialogauth_success
flowchart TD
A[Claude Code 是否触发 hook] --> B[settings.json 是否被正确读取]
B --> C[命令路径是否正确]
C --> D[Python 脚本是否正常执行]
D --> E[Webhook 和 Secret 是否正确]
E --> F[网络或代理是否稳定]
F --> G[飞书是否成功收到消息]
八、总结
这次把 Claude Code 的通知体系完整打通之后,我觉得最关键的几点经验可以概括为:
-
Stop和Notification完全不是一回事。
前者更适合“回复结束”,后者更适合“需要你注意”。 -
远程 Linux 最大的坑通常是代理继承。
如果服务器开了 Clash 或系统代理,Python 的urllib很可能会自动走代理,导致 webhook 偶发失败。 -
Windows 本地最大的问题通常是路径和编码。
路径要加引号,最好使用正斜杠;文本要经过安全转换,避免 surrogate 字符导致整个 hook 报错。 -
先单独打通“脚本 → 飞书”,再接入 Claude Code。
这是整个排查流程里最节省时间的一步。
如果你的需求和我一样:
- 远程服务器跑 Claude Code;
- 本地终端或 VS Code 跑 Claude Code;
- 不想一直盯着终端;
- 希望任务完成后立即收到提醒;
那么这套方案基本已经足够稳定,而且维护成本也不高。
至此,Claude Code 在我的日常使用中,终于从“需要一直看着它”的工具,变成了一个真正可以挂着跑、等消息回来再接手的工作伙伴。


