#OpenClaw #微信 #调试踩坑

记录一次通过微信发送文件附件(xlsx)时遭遇的连环报错,以及最终定位到 iLinkai API 大小写敏感问题的全过程。

本系列部署实录 · 配置续集 · 公网访问 · Headroom 接入 · 后续规划


背景

用 OpenClaw Agent 生成了一份评分标准表格(.xlsx),希望通过微信渠道直接投递给对话方,无需额外步骤。整个流程应该是:

  1. Agent 调用 message 工具,指定 media 参数(文件路径)

  2. openclaw-weixin 插件将文件上传到 iLinkai CDN

  3. CDN 地址写入微信文件消息,发送给对方

听起来很顺,实际踩了好几个坑。


一、连环报错

报错 1:sendMessage ret=-3 errmsg=invalid arguments

1
[tools] message failed: sendMessage ret=-3 errmsg=invalid arguments

原因:指定了错误的 accountId——两个机器人账号中,目标用户只与其中一个有聊天记录,用了另一个账号发送自然被拒。

报错 2:weixin not configured

1
[tools] message failed: weixin not configured: please run `openclaw channels login --channel openclaw-weixin`

原因:accountId 填写了 "default" 这个逻辑名称,插件实际使用运行时账号 ID,无法识别字符串 "default"

报错 3:cannot determine which account to use

1
2
weixin: cannot determine which account to use for to=<OPENID>
(2 accounts registered, none has an active session with this recipient)

原因:两个机器人账号都没有与该用户的活跃 session,插件无法自动判断用哪个账号发送。解决方法是让用户先从微信发一条消息过来,触发 session 建立。

报错 4:getUploadUrl returned no upload URL(持续失败)

1
[tools] message failed: uploadFileAttachmentToWeixin: getUploadUrl returned no upload URL

文字消息已经可以正常发出,但文件发送始终在这一步失败。看起来是接口问题——但为什么同样的配置文字能过、文件不行?


二、从日志中发现成功案例

翻 Docker 日志时,发现同一天早些时候有一次成功记录:

1
2
[weixin] sendWeixinMediaFile: file upload done filekey=<KEY> size=8208
sendFileMessageWeixin: success to=<OPENID>

成功发送的是另一个版本的同类文件。对比成功和失败时的请求参数,发现了关键差异:

场景 target 参数
✅ 成功(13:13) <OPENID_原始混合大小写>
❌ 失败(15:xx) <openid_全小写>

三、根本原因:getuploadurl API 大小写敏感

深入分析插件代码(@tencent-weixin/openclaw-weixin v2.4.6):

1
2
getUploadUrl → ilink/bot/getuploadurl
参数:to_user_id = <用户 openid>

to_user_id 使用全小写 openid 时,API 返回 {"ret":-1},没有错误说明,只有一个 -1

to_user_id 使用原始混合大小写 openid(即 getupdates API 返回的原始格式)时,正常返回 upload_param,上传成功。

而文字消息的 sendmessage 接口对大小写相对宽松,全小写也能发出去。所以才出现了「文字消息能发、文件发不了」的奇怪现象。

结论:ilink/bot/getuploadurlto_user_id 参数严格区分大小写。


四、修复方法

用正确大小写的 openid 重试,约 6 秒内上传完成,消息发送成功。

实际操作建议

  • 不要手动拼写 openid——从 incoming 消息上下文中取原始值,保持格式不变

  • 不要指定 accountId——让插件通过 context-token 自动解析(resolveOutboundAccountId),它会匹配有活跃 session 的账号

  • 文字消息对大小写宽松,文件上传严格,两者要区别对待


五、附:插件文件上传流程

openclaw-weixin 发文件的完整链路如下:

  1. 读取文件,计算 MD5 和 AES-128-ECB 加密后的大小

  2. 调用 ilink/bot/getuploadurl 获取 CDN 预签名参数

  3. 将文件内容加密后 PUT 到 CDN

  4. 调用 ilink/bot/sendmessage,附上 CDN 引用,发出文件消息

第 2 步是这次问题的发生点。对于图片类型(IMAGE)和普通文件(FILE)都走同一流程,都受大小写限制影响。


六、调试技巧

插件有独立日志文件,路径通常在 /tmp/openclaw/openclaw-<date>.log(容器重启后丢失)。日志为 JSON 格式,包含完整的 resp= 字段,能看到 API 原始返回,比 Docker 日志详细得多:

1
2
"uploadFileAttachmentToWeixin: getUploadUrl returned no upload URL
(need upload_full_url or upload_param), resp={\"ret\":-1}"

遇到文件投递问题,优先查这个日志。


七、主动投递恢复:真实主机与 contextToken(OC-19)

后续系统心跳主动投递失败时,发现插件实际访问的是 ilinkai.weixin.qq.com。只把通用微信 API 主机加入直连列表,并不能覆盖插件的真实请求路径。修复只向既有 NO_PROXY
追加这一精确主机,保留代理的其他行为,并仅重建 OpenClaw 单容器。

网络恢复后,第一次主动发送仍失败,因为此前保存的 contextToken 已过期。用户从微信发送一条新消息后,插件从入站上下文刷新 token;随后一次主动 heartbeat 在约 9.6 秒内完成,状态为 delivered=true。06:00 与 18:00 两项系统心跳均保持 enabled 和Asia/Shanghai,无需重新扫码。

这里形成两条独立规则:

  1. 网络旁路必须依据运行日志中的真实目标主机做最小追加,不能用宽泛域名或关闭代理代替;

  2. 主动投递必须使用最近一次入站消息建立的会话上下文,不能持久化或手工拼接 token。

本文不记录 token、联系人原始标识或其他凭据。模型路由与手动 GPU 模式见《OpenClaw 模型路由与 Guardian 交接》


延伸阅读