☰
微信公众号草稿箱接入避坑指南:从 40125 到 40001 的实战排障复盘
2026/10/1 10:25:21 网站建设 项目流程

一、起点:一个看似简单的需求

事情的开头很朴素:我希望让运行在 OpenClaw 上的 AI Agent(代号 ClawBot,我管它叫"龙虾")能直接把写好的文章推进我公众号的草稿箱里。不用登录后台、不用复制粘贴,一条指令下去,草稿就在那儿等着我审。

需求本身不复杂。微信官方有草稿箱接口cgi-bin/draft/add,有素材上传接口,有获取 token 的接口。按文档走,一小会儿应该就能搞定。

结果从上午 10:44 到下午 15:05,整整花了四个多小时。中间换了四个方向的修复,每一个都以为是终点,结果都不是。

以下的复盘内容,就是这四个坑的实战记录。


二、第一个坑:旧端点的误导性错误码

最初的publish.sh用的是微信最经典的 token 获取方式:

GET https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=xxx&secret=xxx

这个接口从公众号开发的第一天起就存在,几乎所有教程都用它。但这次它返回了:

{"errcode":40125,"errmsg":"invalid appsecret rid: ..."}

40125的意思是 AppSecret 无效。于是我做了最直觉的事:检查 secret。

我把.env、TOOLS.md、gateway.systemd.env,以及一个旧版本残留的 secret,一共四个候选值,全部拿去测了一遍。结果四个全部报40125。

四个不同来源的密钥,全部无效——这本身就反常。要么四个都错了(不太可能),要么问题根本不在密钥。

我换了微信后来推出的新端点试了试:

POST https://api.weixin.qq.com/cgi-bin/stable_token Body: {"grant_type":"client_credential","appid":"xxx","secret":"xxx","force_refresh":false}

同一个 secret,返回:

{"access_token":"...","expires_in":7200}

Token 长度 137,拿它去调draft/count立刻成功。

结论很清楚:secret 没错,是旧端点在作妖。

微信官方文档其实早有说明:stable_token与cgi-bin/token互相隔离,推荐使用前者替代后者。旧端点对某些 AppID 会返回误导性的40125,而真正的凭证验证在新端点上是完全正常的。

这是第一个坑:你以为是密钥错了,其实是端点变了。


三、第二个坑:force_refresh 的隐性互斥

把publish.sh的 token 端点迁到stable_token后,我以为通了一半。结果发布依然失败:

40001: invalid credential, access_token is invalid or not latest

40001是 token 失效或不是最新的。但 token 明明是刚取回来的。

我一度判断"stable_token 返回的 token 立即被判 invalid",甚至写进了报告。这个判断后来被推翻了。

真正的原因藏在调用参数里:当时用了force_refresh: true。

微信官方对stable_token的参数有明确说明:

普通模式(force_refresh: false),access_token 有效期内重复调用不会更新;
强制刷新模式(force_refresh: true),会导致上次获取的 access_token 失效,并返回新的。

而我当时在调试过程中,同时在多个终端、多个脚本里调用 token 接口,有的用了true,有的用了false。每一次true都在作废前一个 token,导致所有持有旧 token 的调用瞬间40001。

改成force_refresh: false后,token 立刻稳定可用,连续两次draft/count都返回total_count。

这是第二个坑:参数选错,token 自己在跟自己打架。


四、第三个坑:被 shell 吞掉的那个变量

到这一步,stable_token端点正常,token 获取正常,只剩最后一步:把 token 交给wenyan-cli,让它去发布。

wenyan-cli有一个导入外部 token 的命令:

wenyan token -i --app-id "$WECHAT_APP_ID" --token "$token"

运行后,命令报告"导入成功"。但发布依然40001。

我去看~/.config/wenyan-md/token.json,发现里面存的是:

{"appid":"wx...","accessToken":"***","expireAt":-1}

accessToken字段的长度是3。也就是说,导入进去的是一个空占位符,真正的 token 值根本没写进去。

问题出在命令本身。当时脚本里的写法是:

wenyan token -i --app-id "$WECHAT_APP_ID" --token "***"

这个***不是占位符——它是被 shell 处理后的真实结果。应该是在某个环节(可能是多层变量嵌套、可能是工具链对$(...)的改写)把 token 变量的值吞掉了,传给wenyan的实际上是一个空字符串,或者 3 个字符的垃圾值。

改成双引号包裹的明确变量后:

wenyan token -i --app-id "$WECHAT_APP_ID" --token "$token"

token.json里立刻写入了完整的 137 位 token,发布成功,Media ID 正常返回。

这是第三个坑:命令报告成功,不代表数据真的写进去了。中间层吞了你的变量,你连尸体都找不到。


五、第四个坑:三方争抢同一张票

到这里,如果你以为问题全解决了,那还太早。

发布成功后,我想用draft/batchget核验一下草稿是否真的入箱。结果又报40001。

同样的 token,几分钟前还在用,现在就失效了。

原因是在整个系统里,有三个组件都在独立获取 token:

  1. 我手动curl调试时取的 token;
  2. publish.sh内部每次运行都取一次 token;
  3. wenyan自己持有一份导入的 token(虽然expireAt: -1已禁用自管,但它仍然拿着旧的那份)。

任何一方取了新 token,微信就会判定上一个失效。结果就是:每个组件单独测都通,凑在一起随机40001。

这跟微信官方推荐的做法直接冲突。官方文档明确写过:

建议开发者使用中控服务器统一获取和刷新 access_token,其他业务逻辑服务器所使用的 access_token 均来自于该中控服务器,不应该各自去刷新,否则容易造成冲突。

解决思路只有一个:确立唯一持票人。

最终架构定为:

  • publish.sh是唯一取 token 的组件;
  • 它取到 token 后导入wenyan;
  • 其他所有核验、调试操作,一律从wenyan的token.json里读取,不再独立取 token;
  • 脚本内部增加 token 复用探测:如果token.json里的 token 长度大于 100,先拿它调一次draft/count,通过就复用,不通过才走stable_token刷新。

改完之后,串行验证一次通过:publish.sh发布验收稿,紧接着从token.json读 token 做batchget核验,没有互斥,草稿准确入库。

这是第四个坑:不是你取不到 token,是你取太多次了。


六、最终架构与验收

凭证唯一真源

~/.openclaw/.env是唯一的凭证来源:

  • 优先读WECHAT_APP_ID/WECHAT_APP_SECRET;
  • 兼容旧变量名WECHAT_SECRET,但命中时输出 deprecation 提示;
  • 废弃TOOLS.md中的旧密钥,脚本不再读取。

token 流程(唯一持票人)

  1. 先读~/.config/wenyan-md/token.json中的accessToken;
  2. 长度大于 100 时,用它调draft/count探测;
  3. 探测通过则复用,跳过取 token;
  4. 无效或缺失时,才走stable_token(POST + JSON +force_refresh: false);
  5. 用wenyan token -i --app-id "$APP_ID" --token "$token"导入;
  6. 导入后校验token.json中accessToken长度应为 137。

最终验收

  • 草稿标题:《【验收】微信公众号草稿箱打通验证 2026-09-29》
  • 草稿箱状态:已收到
  • 发布退出码:0
  • 唯一持票人架构:已固化进publish.sh
  • 日志归档:skills/wechat-publisher/reports/2026-09-29-selfcheck/

七、实战检验:鲁迅风亲子稿

链路打通后,我做了一次真实测试:让 ClawBot 用鲁迅的文笔,写一篇关于"女儿掉门牙、英语发音漏风"的文章,不少于 700 字,直接推到草稿箱。

它写出来的题目是《缺了门牙的日子,是时间漏出的光》。主线是"掉门牙 → 英语发音漏风 → 全家和师生的笑 → 新牙长出的怅惘",把孩子的成长写成"嘴里开着的一扇小窗",结尾落在"漏风的年月,反成了心上最完好的一颗牙"。

文章入箱,后台确认通过。这是公众号里第一篇由 AI 协助完成的鲁迅风亲子稿。

技术上的意义是:链路不只是能跑通 demo,它更能承接真实的写作任务,并产出可以人工验收的成品。


八、可复用的经验

如果只记五条:

  1. cgi-bin/token正在被弃用,尽快迁移到stable_token。两者互相隔离,不要混用。
  2. stable_token普通模式用force_refresh: false。强制刷新会立即作废旧 token,只在明确需要时使用。
  3. access_token 必须单持票人。多个组件各自刷新 token,必然互斥。中控服务器模式不是建议,是必须。
  4. wenyan token -i导入后,立即验证token.json中accessToken的长度。应为 137,不是 3。命令报告成功,不代表数据真的写进去了。
  5. 调试时先确认变量本身有值,再查下游。echo "len=${#token}"一行命令,能省掉两小时。

九、结尾

这次排障最值得记录的一点,不是某个错误码怎么处理,而是错误码会撒谎。

40125说是密钥错,其实端点变了。40001说是 token 失效,其实是force_refresh在互斥。wenyan token -i说导入成功,其实写了个空值。

每一层都给你一个看似合理的解释,让你沿着错误的方向修下去。唯一能做的,是每一层都回到最原始的验证:这个 token 真的能用吗?这个变量真的有值吗?这个命令真的写进去了吗?与其抱怨,不如养成一个习惯:怀疑每一个报错的字面意思,验证每一个中间态。

技术排障写到这里——通过实战验证获得的那篇《缺了门牙的日子,是时间漏出的光》公众号文章,能落到一个七岁孩子漏风的笑声里,也算是诠释了技术进步的别样浪漫。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询