尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

Claude Code 通知总骗我?用 Stop hook + TaoToken 把 VS Code 提醒接进 Telegram

发布时间:2026/9/26 17:17:04

资讯中心
01
ARTICLE

Claude Code 通知总骗我?用 Stop hook + TaoToken 把 VS Code 提醒接进 Telegram

Claude Code 通知总骗我?用 Stop hook + TaoToken 把 VS Code 提醒接进 Telegram
1. 为什么 Claude Code 的「完成」通知总在骗你如果你同时开着好几个 VS Code 窗口每个窗口里挂一个 Claude Code 会话那你大概率经历过这种场景手机弹出一条「任务完成」你切过去一看终端还在滚日志Bash 命令还在后台跑子代理还在默默干活。你以为它骗你其实是你接的那个 Stop hook 太天真了。问题的根源在于 Claude Code 的 Bash 工具。当它以run_in_backgroundtrue模式运行时会立刻返回一条确认信息类似Command running in background with ID: xxx。这条确认是在命令被派发的那一刻返回的不是执行完的时候。一个天真的 Stop hook 只要匹配到工具结果就当成「已解决」就会把这条确认误读成完成。后台 Agent 子代理还有另一种角度的同样问题它们根本不会发出确认所以只追踪明确「完成」信号的钩子压根看不到它们。结果就是恰恰在你最在意的那类场景——长的、多步骤的、大量依赖后台任务的回合——这个钩子比什么都没有还糟。它在你最需要准确提醒的时候给你一条假消息。这篇要解决的问题很具体让 Claude Code 在 VS Code 里真正跑完、真正卡住、真正报错的时候通过 Telegram 给你一条可信的通知而不是一条「狼来了」。核心手段是 Stop hook SubagentStop hook 配合会话记录解析再通过 TaoToken 统一管理 API Key 和模型接入让整个链路可复制、可验证。适合谁并行跑多个 Claude Code 会话的开发者、依赖长时间自主回合的 TDD 工作流用户、以及被「假完成」通知坑过的人。2. 先把 TaoToken 的 Key 和 API 配好在写 hook 之前得先保证 Claude Code 本身能稳定调用模型。我试过把 Key 散落在各个项目的环境变量里换一个窗口就要重新配一次后来统一走 TaoToken 的 API 入口一个 Key 管所有会话。TaoToken 的 API 地址是https://taotoken.net/api注意这个地址不带任何查询参数。你需要在 Claude Code 的配置里把 base URL 指向它然后用同一个 Key 去调不同的模型。这样做的好处是hook 脚本里不需要再关心模型是哪个、Key 是哪个通知逻辑和模型调用解耦。具体操作分两步。第一步去控制台创建一个 API Key地址是https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentconsole。创建完之后复制那串 Key后面配置里要用。第二步在 Claude Code 的 settings.json 里配置 API 端点。这个文件通常在~/.claude/settings.json如果你用的是项目级配置就在项目根目录的.claude/settings.json。配置片段如下{ env: { ANTHROPIC_BASE_URL: https://taotoken.net/api, ANTHROPIC_API_KEY: sk-你的TaoToken密钥 } }这里有个坑要注意ANTHROPIC_BASE_URL后面不要加/v1或者任何路径TaoToken 的 API 入口已经处理好了路由。加了反而会 404。配好之后你可以先用一条 curl 验证 Key 是否有效别等到 hook 写完才发现 Key 是错的。curl -s -X POST https://taotoken.net/api/v1/messages \ -H x-api-key: sk-你的TaoToken密钥 \ -H anthropic-version: 2023-06-01 \ -H content-type: application/json \ -d { model: claude-sonnet-4-20250514, max_tokens: 32, messages: [{role: user, content: ping}] }如果返回里带content字段说明 Key 和端点都通了。这一步过了再往下走否则后面 hook 调不通你会以为是 hook 的问题。3. 可复制的 settings.json hook 骨架现在进入正题。Claude Code 的 hook 配置写在 settings.json 的hooks字段里。我们要接两个事件Stop和SubagentStop。但前面说了原生的 SubagentStop 对后台代理不可靠所以真正的判断逻辑要放在脚本里通过解析会话记录来算 pending 和 resolved。先看 settings.json 的完整骨架{ env: { ANTHROPIC_BASE_URL: https://taotoken.net/api, ANTHROPIC_API_KEY: sk-你的TaoToken密钥 }, hooks: { Stop: [ { matcher: , hooks: [ { type: command, command: python3 ~/.claude/hooks/notify_stop.py } ] } ], SubagentStop: [ { matcher: , hooks: [ { type: command, command: python3 ~/.claude/hooks/notify_subagent.py } ] } ] } }matcher留空表示匹配所有情况。command指向你的 Python 脚本。这里用 Python3 是因为它解析 JSON 会话记录最方便而且 macOS 和 Linux 都自带。接下来是notify_stop.py的核心逻辑。它的任务是读取当前会话的 transcript 文件找出所有run_in_backgroundtrue的 Bash 调用和子代理调用然后检查有没有对应的task-notification携带原始tool_use_id。只有全部 resolved 了才发 Telegram。#!/usr/bin/env python3 import json import os import sys import urllib.request TRANSCRIPT_DIR os.path.expanduser(~/.claude/projects) TELEGRAM_TOKEN os.environ.get(TELEGRAM_BOT_TOKEN, ) TELEGRAM_CHAT_ID os.environ.get(TELEGRAM_CHAT_ID, ) def load_transcript(session_id): # 遍历项目目录找到对应 session 的 jsonl 文件 for root, dirs, files in os.walk(TRANSCRIPT_DIR): for f in files: if session_id in f and f.endswith(.jsonl): path os.path.join(root, f) with open(path, r, encodingutf-8) as fh: return [json.loads(line) for line in fh if line.strip()] return [] def compute_pending(records): launched set() resolved set() for rec in records: # 收集后台 Bash 的 tool_use_id if rec.get(type) tool_use: inp rec.get(input, {}) if inp.get(run_in_background) is True: launched.add(rec.get(id)) # 收集 task-notification 里携带的原始 id if rec.get(type) task-notification: resolved.add(rec.get(tool_use_id)) return launched - resolved def send_telegram(text): if not TELEGRAM_TOKEN or not TELEGRAM_CHAT_ID: return url fhttps://api.telegram.org/bot{TELEGRAM_TOKEN}/sendMessage payload json.dumps({chat_id: TELEGRAM_CHAT_ID, text: text}).encode() req urllib.request.Request(url, datapayload, headers{Content-Type: application/json}) urllib.request.urlopen(req, timeout10) def main(): raw sys.stdin.read() event json.loads(raw) if raw.strip() else {} session_id event.get(session_id, ) records load_transcript(session_id) pending compute_pending(records) if pending: # 还有后台任务没回来保持沉默 sys.exit(0) cwd event.get(cwd, unknown) send_telegram(fClaude Code stopped | {cwd}) if __name__ __main__: main()notify_subagent.py可以复用同一套逻辑只是触发时机不同。关键点在于不要相信 SubagentStop 事件本身携带的完成信号而是回到会话记录里按tool_use_id匹配。这是目前唯一靠得住的信号。Telegram 的 Bot Token 和 Chat ID 不要写死在脚本里用环境变量注入。你可以在~/.claude/hooks/config.env里存然后在 shell 启动脚本里 source 它。这样 token 不会被误提交到 git。4. 验证通知真的到达而不是误报配好之后别急着跑长任务。先用一条 curl 验证 Telegram 链路本身是通的curl -s -X POST https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendMessage \ -d chat_id${TELEGRAM_CHAT_ID} \ -d textClaude Code notify test手机收到这条消息说明 Bot 和 Chat ID 没问题。收不到就检查 Bot 有没有被拉进目标会话、Chat ID 是不是对的。然后验证 hook 逻辑。开一个 Claude Code 会话让它跑一个后台命令# 在 Claude Code 里输入 run this in background: sleep 30 echo done这时候 Stop hook 会触发但因为sleep 30还在跑compute_pending会返回非空集合脚本保持沉默。你应该收不到任何通知。等 30 秒后命令真正结束会话记录里出现task-notification再触发一次 Stop这时候才会发消息。如果你在 30 秒内就收到了「完成」说明 pending 计算有问题大概率是tool_use_id匹配没对上。检查会话记录里task-notification的字段名是不是tool_use_id不同版本的 Claude Code 可能有细微差异。再验证错误场景。故意让一个回合报错比如在 Claude Code 里请求一个不存在的文件路径触发错误终止。这时候错误 hook 应该发一条带项目路径和回合标题的消息。这条消息的价值在于当你的账号撞上用量限制、多个会话同时卡住的时候你能在手机上一次性看到哪些项目、哪些任务出了问题而不是一个个窗口点进去撞红墙。5. 本篇常见错排查通知来得太早命令还在跑。这是最典型的症状原因是 hook 把 Bash 的即时确认当成了完成。检查你的compute_pending是不是只收集了run_in_backgroundtrue的调用以及task-notification的匹配字段是否正确。如果匹配字段写成了id而不是tool_use_id就会永远算不出 resolved导致 hook 永远沉默——这比误报好但也不对。SubagentStop 完全不触发。后台代理会绕过 Stop 和 SubagentStop这是 Claude Code 本身已知的行为不是你配置的问题。所以不要依赖 SubagentStop 来做完成判断把它当成一个辅助信号真正的判断回到会话记录解析。Telegram 收不到消息但 curl 能通。检查 hook 脚本里的环境变量有没有被正确加载。Claude Code 启动 hook 时的环境变量继承自父进程如果你在.zshrc里 export 了 token但 Claude Code 是从 GUI 启动的可能读不到。稳妥做法是把 token 写在~/.claude/hooks/config.env在脚本开头手动读取。会话记录找不到。TRANSCRIPT_DIR的路径在不同系统上不一样。macOS 和 Linux 通常在~/.claude/projectsWindows 在%USERPROFILE%\.claude\projects。如果你的 Claude Code 版本改了存储位置用find ~ -name *.jsonl -path *claude*找一下实际路径。Key 配了但模型调不通。回到第 2 节的 curl 验证。如果 curl 返回 401检查 Key 有没有复制完整如果返回 404检查ANTHROPIC_BASE_URL是不是多加了/v1。TaoToken 的 API 入口是https://taotoken.net/api不要画蛇添足。6. 把通知接进你的工作流整套东西跑通之后你的工作方式会变起一个长任务切到另一个项目然后不再去想它。会有一条消息传来而且只会因为三种原因之一——这一回合真的完成了包括所有后台任务它需要你输入或者它出错终止了。不会有东西还在跑的时候提前收到「完成」也不用手动挨个检查五个会话找哪个卡住了。如果你还没配 TaoToken 的 Key先去https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentapi-keys创建一个然后按第 2 节的片段配到 settings.json。接入文档在https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentdoc里面有不同客户端的配置示例。如果你主要用 Claude Code 做长期编码和 Agent 任务可以考虑 Coding Plan地址是https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentcoding-plan它针对长时间会话做了额度优化。想先验证模型对话是否正常用https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentmodel-chat发一条消息试试。最后提醒一句hook 脚本里的 Telegram token 和 TaoToken Key 都不要提交到 git。用config.env加.gitignore是最省事的做法。改主意了想撤掉 hook把 settings.json 里的hooks字段删掉再删掉脚本文件就行不会影响 Claude Code 本身。
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

◈

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

◐

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

▲

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。