想把 Claude 接进微信的人,多半卡在两处。一是路子:网上教的大多是「再起一个 API 机器人」,可你想要的是终端里那个 Claude Code 本身——订阅、全套工具、你的 CLAUDE.md、你们攒下的记忆,原样搬进微信。二是接上之后:头一个小时好好的,然后微信里突然什么都收不到了,日志一行错都没有——这半个月腾讯官方仓库里同样的 issue 堆了十几条,我也把自己的门砸哑了一回。这篇两件事都讲:怎么用 Claude Code 官方的 channels 加腾讯官方的 iLink Bot API 扫个码接上,以及那道静默风控长什么样、怎么判、怎么躲。全是实测,脚本在 script/,我自己家就跑着这一份(去掉了名字)。
「把 Claude 接进微信」有两种完全不同的做法:
这篇只讲第二种。它靠的是两样官方的东西拼起来:Claude Code 的 channels(Anthropic 3 月 20 日起的 research preview)和腾讯的 iLink Bot API(就是官方 @tencent-weixin/openclaw-weixin 插件用的那套接口)。不 hook 微信客户端、不是 iPad 协议、没有第三方中间层。
前提很短:Claude Code 2.1.2xx 以上(二进制里有 channels 这几个字);Node 22 以上(Node 26 能直接跑 .ts,最省事;用 bun 也行);一个微信号;macOS 或 Linux。
一个本地 MCP 服务器,在 capabilities 里声明 experimental: { "claude/channel": {} },然后用 notifications/claude/channel 把外面的消息推进正在跑的交互式会话。会话里看到的是这样一段:
<channel source="wechat" sender="o9cq…" sender_id="o9cq…" msg_type="text" can_reply="true">
在地铁上睡着了!
</channel>
source 就是你 mcp.json 里给这个服务器起的键名。Claude 读到它,调这个服务器暴露的工具(这里叫 reply / send_image)回话。
两条要紧的:
--dangerously-load-development-channels server:<键名>。名字吓人,但这是官方文档里给「开发中的渠道」留的正门,不是黑路。首启会弹一个确认框,选「1. I am using this for local development」。腾讯 2026 年给个人微信开的官方 bot 通道,域名 ilinkai.weixin.qq.com。一共就这几个接口:
| 接口 | 干什么 |
|---|---|
get_bot_qrcode / get_qrcode_status | 出二维码、轮询扫码状态,确认后换一枚 bot_token |
getupdates | 长轮询收信(35 秒一轮),每条来信带一枚 context_token |
sendmessage | 回信,必须带来信那枚 context_token |
getconfig + sendtyping | 让对方看到「正在输入」 |
getuploadurl + CDN | 发图:AES-128-ECB 加密后传 CDN,再发一条引用 media_id 的消息 |
两条规矩决定了它的形状:
context_token。对方不先说话,你一个字发不出去。所以它不是推送通道,是「她说一句、你回一句」的门。iLink-App-Id: bot、iLink-App-ClientVersion: 132104(= (2<<16)|(4<<8)|8,即 2.4.8),body 里 base_info.bot_agent 自报家门。git clone https://github.com/sanqianzilanyue/claude-code-in-wechat.git ~/claude-code-in-wechat
cd ~/claude-code-in-wechat/script
npm i # 只有三个依赖:@modelcontextprotocol/sdk、qrcode、qrcode-terminal
wechat-channel.ts 是渠道本体,上游是 Johnixr/claude-code-wechat-channel(MIT),我改了一圈:门禁、收图落盘、腾讯八种扫码状态、回包校验、发信账本、回执号判据(第 6 节)。setup.ts 是独立的扫码工具。
node setup.ts
终端会印二维码、印一条 liteapp.weixin.qq.com/q/… 的链接(微信自家链接,发到手机上点开直接跳微信),还会存一张 二维码.png——人不在电脑前的时候把图发到手机、从相册扫。手机微信扫码、确认,凭据落在 ~/.claude/channels/wechat/account.json(0600,别进 git)。
扫码状态腾讯有八种,上游只认四种,缺的那几种会让你干等到超时:
| 状态 | 意思 | 脚本怎么办 |
|---|---|---|
wait / scaned / confirmed | 等扫 / 已扫等确认 / 成功 | 正常流程 |
expired | 码两三分钟就过期 | 自动换新码,PNG 原地重写,最多六张 |
scaned_but_redirect | 这次登录被派到另一个机房 | 换 redirect_host 接着轮 |
need_verifycode | 手机微信上显示了几位数字 | 念进终端(或写进 WECHAT_VERIFY_FILE 指的文件) |
verify_code_blocked | 数字错太多次 | 锁了,过会儿重扫 |
binded_redirect | 这个微信已经绑过这台机器 | 老凭据还有效,不用重扫 |
bash start.sh
# 等价于:
# claude --mcp-config mcp.json --strict-mcp-config --settings settings.json \
# --dangerously-load-development-channels server:wechat
start.sh 多做了三件事:固定一个会话号(--session-id,写在 session.id 里)、重开时自动 --resume 同一扇窗、起窗前 unset 所有 CLAUDE* 环境变量(第 4 节讲为什么)。首启答一次「local development」的确认。横幅里若有一行 server:wechat · no MCP server configured with that name,是误报——消息照进、工具照用,别追。
微信里会多出一个「ClawBot」对话。给它发一句。第一个来说话的微信会被认作主人(owner.json),往后群聊一律不进门、生人静默不开。终端里立刻出现 <channel …> 标签,Claude 调 reply 回你。从这句起:语音自动转成文字、图片和文件落到 ~/.claude/channels/wechat/media/ 并把路径挂在标签的 media_path 上、can_reply="false" 表示这条暂时回不了(还没拿到令牌),等下一条。
MCP 服务器的 instructions 会进这扇窗的系统提示,等于给 Claude 一张「微信怎么说话」的小抄。我这份的骨头(在 wechat-channel.ts 里,照你的口味改):
reply,sender_id 照标签传;can_reply=false 时别调,等下一条。media_path 看过再回。texts 数组,最多两条(为什么这么抠,看第 6 节)。reply 工具带一个 texts 数组:几条连发放进去一次调用,每条之前亮一下「正在输入」、按字数停半秒到两秒半,像真人在打字。这是我一开始最得意的手感,后来也是最先被砍掉的东西——原因在第 6 节。
这扇窗的价值在于它一直是同一扇:记忆、上下文、你们聊到哪了,都在里面。几条家法:
--resume。 start.sh 把 uuid 写进 session.id,重开先 --resume,续不上(比如从没说过话)就用同一个号新开。screen -dmS wechat bash start.sh,screen -r wechat 看一眼,Ctrl-A D 退出不关窗。unset 所有 CLAUDE* 环境变量。 如果你是从另一扇 Claude Code 窗的 Bash 里起的 screen,它会继承 CLAUDE_CODE_CHILD_SESSION 之类的变量,被当成子会话——横幅里写着「Transcript saving is off」,转录不存、下次 --resume 续不上。~/.claude.json 或项目的 .mcp.json:任何一扇挂了它的窗都会去轮询同一枚 token,你的消息会被别的窗吃掉。settings.json 里 deny 掉一堆聊天用不上的内置工具(Agent、Workflow、Cron…),说明书不进提示词,窗子瘦一圈。stuff。连在一起的 \r 会被当成粘贴里的换行,话卡在输入框里不发。/exit 没生效又起一扇,就是两个 claude 顶着同一个会话号,screen 命令全歧义。查活着的窗:ps -ax -o command | grep -E 'claude (--session-id|--resume)'。| 现象 | 病根 | 药 |
|---|---|---|
第一句回不出,工具报 no context_token | 腾讯私聊消息带 group_id: "",上游 groupId ?? senderId 把空串当有值,令牌记到了空键下 | msg.group_id || undefined |
所有请求 fetch failed | Node 24+ 的 undici 拒绝手写 Content-Length(腾讯官方包 2.4.2 也栽过) | 删掉手写的头,别加回 |
转录不存、--resume 续不上 | 继承了别的窗的 CLAUDE_CODE_CHILD_SESSION | 起窗前 unset |
| 二维码扫了没反应 | 码两三分钟过期,或状态是上游不认的那四种 | 第 2.2 节那张表 |
「回了」但对方说没到,日志 ret 非零 | 上游 sendmessage 不看回包,{"ret":-2} 也当成功 | assertSendOk:ret/errcode 非零即抛,逐条落日志 |
errcode: -14 | token 失效(session timeout) | 重扫 |
| 长回复整条丢 | 腾讯仓库 #284:长文本 ret=-2 "prepare failed" | 一条两三句,别写长文 |
| 「回了」但对方什么都看不见,日志一行错都没有 | 风控 | 第 6 节 |
四条同时成立,就是它:
getupdates 正常,<channel> 照进)。sendtyping 正常)。sendmessage 返回 HTTP 200、{"ret":0}——接口说收下了。门是下午 16:14 扫通的,16:27 第一轮四条回到她手机上,到 17:26 六轮 23 条全到。17:36 那一轮,四条只到了三条。然后我干了一件现在看很蠢的事:为了查「为什么少了一条」,我用四轮探针脚本往她的真号里砸了四十多条测试消息——测时窗、测令牌新鲜度、测长连接、测 client_id 前缀。越测送到的越少,最后一条都不到。
我当时得出的结论是「腾讯每小时只送约 19 条」,还写了一本按小时计数的出信账本。这个结论是错的。 真相是:几分钟内的连发把腾讯的风控砸响了,而风控一旦响起,是黏的——几十分钟到几小时,甚至不再恢复;我每砸一条探针,都在把嫌疑坐实。晚上她从小红书上翻到一篇帖子,说这几天很多人和她一样,问我「是不是被风控了」。是。
腾讯官方仓库 Tencent/openclaw-weixin 从 8 月 19 日起,同一个症状的 issue 一路排下来:#261、#263、#264、#266、#268、#270、#273、#278、#279、#280、#285,跨 Windows / Linux / macOS、跨 VPS / 本地、跨 OpenClaw / Hermes / 自研客户端。小红书上 8 月 31 日那篇《你是在什么环境部署微信 clawbot?》底下四十几条评论,也是同一件事。
9 月 1 日,仓库的协作者在 #278 给了目前唯一一段接近官方的话(原文英文,我译):
ret=0表示请求在 API 层被成功接受并处理,不保证消息会被送达或显示在对方的微信客户端上。出于用户体验、平台安全与反滥用的考虑,微信可能会对违反相关策略的账号和消息静默限制或过滤;这种情况下 API 仍可能返回成功,而消息不会送达。微信 ClawBot 支持再次绑定,但会自动解绑前一个 linkbot。请注意:一些可能对用户产生安全或体验影响的、诱导性分享的扫码绑定,可能会被识别为安全风险。
也就是说:这是服务端的、按账号(或按「微信用户 ↔ bot」这对绑定)的静默限制,不报错、不给冷却时间、没有申诉入口。 你的代码没坏,你的微信也没被封——只是这一对绑定被悄悄放进了抽屉里。
#280 做了一个对照实验,也是我现在唯一信的判据:
sendmessage 回包 | 实际 | |
|---|---|---|
| 健康的绑定 | ret=0,带 message_id | 送达 |
| 被限制的绑定 | ret=0,光秃秃的 {"ret":0} | 不送达 |
我翻自己的日志:17:40 起六次排障回包,全是光秃秃的。所以脚本里现在有这一段——只判不拦,把结果写进日志和工具结果,让会话里的 Claude 别对着空洞一条条补发:
function assertSendOk(raw: string, what: string): boolean {
const j = JSON.parse(raw);
if ((j.ret ?? 0) !== 0 || (j.errcode ?? 0) !== 0) {
throw new Error(`${what} 被腾讯拒了: ret=${j.ret} errcode=${j.errcode} ${j.errmsg ?? ""}`);
}
const hasReceipt = !!(j.message_id ?? j.msg_id ?? j.msg?.message_id);
if (!hasReceipt) log(`${what} 腾讯收了但没给回执号——多半被静默限制了,对方看不见`);
return hasReceipt;
}
reply 工具的返回值也跟着变:sent 2(其中 2 条腾讯只回了 ret=0、没给回执号——别补发、别重发,等对方说收到了再当送到)。
把 issue 里几十个人的账合起来:
| 做法 | 结果 |
|---|---|
| 换一个微信号扫码绑定 | 大多数人当场恢复(#264 底下五个 +1、#273、#280、小红书上「小号被风控换大号即好」) |
| 同一个微信号重扫 / 解绑重绑 / 换 bot / 重装插件 | 没用——故障跟着微信用户走(#268 用干净重绑做过对照) |
| 干等 | 有人几小时好了,有人 40 小时还没好(#264),没有规律。我自己这扇:17:36 砸哑,次日 08:40 她一句「快到 site 了」进来、我回的三条全到——约 15 小时自己开了,中间没换号没重扫,只是停手 |
| 换服务器 / 换云厂商 / 换出口 IP | 争议:帖主怀疑机房 IP 被当商业平台;一位跑着上千用户的评论者说一台机器上只有百来人中招,是按用户风控,不是按机器 |
| 9 月 3 日 #285 | 「之前绑定的微信可以用,新扫的全不能用」——换号今天也可能撞墙 |
从这些账里能倒推出腾讯在意什么。我现在守的:
texts 连发最多两条。四五条连发的手感很好,但它长得最像群发。context_token 主动报喜之类的事,能省则省。message_id(6.4)。没有,就是它。一、接的是那扇窗,不是一个机器人。 channels 把消息推进正在跑的交互式会话,你的 CLAUDE.md、记忆、订阅全在;空闲零调用,轮的是腾讯。
二、bot 不能凭空开口。 回信要拿来信的
context_token,所以它是「她说一句你回一句」的门,不是推送通道——设计一切功能之前先认下这条。三、
ret=0不等于送到,message_id才是回执。 静默风控按账号走、不报错、黏得很;一条回完、别拿真号做实验,撞上了先停手再换号。
把 Claude Code 接进微信,技术上只有一个二维码那么难;难的是接上之后学会少说话——腾讯这扇门是给「她说一句、你回一句」留的,你把它当成可以连发五条、可以拿来测试、可以拿来推送的通道,它就悄悄把你关进抽屉,连一声都不响。