说实话,这次把我卡住的,真不是什么大问题。
不是服务挂了。不是流程炸了。也不是那种一看就知道今晚别想睡了的大故障。
就是一个很小的动作:发个文件。
昨晚,我想让openclaw把一份刚生成好的 Markdown 文件发到飞书群里。
结果偏偏就是这一步,把我来回卡了好几轮。
卡的地方不是“发不出去”本身,而是为了弄清楚问题所在。
真正让人上头的是:前面一层要求你必须这么传,后面一层又不支持你这么传。
说白了,这次不是普通的“文件没发出去”。而是接口参数上没对齐。
不带 card,校验就不让过
最开始失败的时候,第一层报错是这样的:
Validation failed for tool "message":card: must have required property 'card'
这条报错的意思,其实很明确。
它不是在说“飞书不会发文件”,而是在说:
当前这个
message工具,在feishu / send这个动作下参数校验器强制要求有
card但我当时是想发文件附件,不是发卡片
所以请求在真正执行之前,就先被卡在了校验阶段
这一步其实已经定位到一个很具体的问题了:发文件这一步的接口参数不兼容。
因为我要走的是“发附件”这条路,但给我的校验条件,却像是在按“发卡片”那套要求拦我。
既然你要求我带 card,那我补一个 `card: {}` 进去,先让它过校验,看后面会发生什么。
结果第一层是过了。但马上又撞上第二层报错:
Feishu send does not support card with media.这句话的意思也很直接:
飞书 plugin 的实际发送实现里
card和media/file这条分支不能同时发所以你即使过了前面的参数校验
后面还是会被实现层拦下来
于是整个事情瞬间变得特别别扭:
不带 card → 参数校验不让过
带 card + 文件 → 飞书 plugin 实现层又不支持
这就不是简单的“参数没传对”了。而是前后两层直接给了你两套互相打架的要求。
也就是典型的——两头堵!!
这时候其实已经定位到问题方向了:
既然第一层报错明确指向 card 被强制要求,那很自然就会怀疑:
是不是飞书插件在定义这套参数规则的时候,把 card 设成了必填?
于是我去改了飞书插件里的定义,把 optional: ["card"] 这部分改为选填,然后重启 gateway,不带card测试。
结果就是:毫无变化。
还是那个校验错误。还是卡在那个地方。
一通操作猛虎,定睛一看原地杵!
说实话,这一步比单纯报错更让人头大。
因为那种感觉不是“我完全不会修”,而是:我明明已经定位到规则层,也改了,为什么一点反应都没有?
后面真正该查的,就不是“逻辑对不对”,而是“谁在生效”
当你改了、重启了,结果报错还是原封不动,这时候就不能再继续靠感觉往下猜了。
因为这通常说明一件事:
你改的那段代码,不一定是真正会生效的那段。
这句话平时看着有点绕,真踩进去的时候就特别具体。
你能看到源码。你能改源码。你改完还重启了。但系统最后到底按哪一份代码生效,不一定是你眼前那份最显眼的。
所以后面排查的重点,已经不是继续猜发送逻辑,而是先确认更底层的问题:
OpenClaw gateway 当前加载的,到底是不是我改的飞书插件?
先去核对 OpenClaw gateway 的插件引用。这一层得先坐实,不然连是不是改对目标都说不清。
查下来之后,发现插件引用没问题。gateway 确实用的是当前这份飞书插件。
那么问题来了:既然插件没用错,为什么我改了 optional: ["card"],还是完全没反应?
继续往下追,才发现事情没那么简单。
飞书插件实际生效的 message tool schema,并不是插件源码里那段。真正起作用的 schema,是 OpenClaw 宿主安装包里的 dist 代码生成的。
我的天!!
查到这里,前面很多别扭的地方一下就通了。
因为真正把 card 顶层定义成必填的,不是飞书 plugin 的发送逻辑本身,而是 OpenClaw 宿主版本里的 schema helper。
也就是说:
我去改飞书插件里的
optional: ["card"],这个动作本身没错但当前宿主版本,根本不会引用那段 optional 信息
真正生效的,是 OpenClaw 宿主 dist 代码生成出来的 schema
而在那段代码里,
card就是必填
所以这次问题真正的根因,不在“飞书不会发文件”,也不在插件发送逻辑本身。
问题出在:OpenClaw 宿主层生成出来的 schema,和飞书 plugin 实现层支持的发送能力,没有对齐。
这里补充一下,我当前用的openclaw版本是:2026.3.23-2
对!就是前面踩坑的那个版本!这不,又遇到一个新坑,还热乎的!
传送门:openclaw 2026.3.23-2 版本更新踩坑记录
这也是为什么它会变成一个特别烦的“两头堵”
现在回头看,整个冲突就很清楚了。
OpenClaw 宿主层在当前版本里,把 card 顶层校验成了必填。
飞书 plugin 实现层,不支持 card + media/file 这条发送组合。
于是结果就是:
你不带
card,过不了 OpenClaw 这边的校验你带了
card + 文件,又过不了飞书 plugin 这边的实现层
从使用体验上看,就是一条本来很普通的“发文件”路径,被前后两层一起堵死了。
而且更烦的是,这个冲突不是第一眼就能看出来的。因为你最开始只会看到“发文件失败”,不会立刻想到:原来是宿主层和插件层在这个参数上根本没对齐。
这次更有价值的,不是“文件最后发出去了”,而是把冲突发生在哪一层查清楚了。
说实话,这类问题最磨人的,不一定是修不修得掉。而是你在前面那段时间里,会反复怀疑自己到底改对了没有。
我前面改飞书插件那一步,其实并不是瞎改。如果当前运行态真按那段 optional 定义来生效,那这个方向本来就应该是对的。
问题在于:当前真正说了算的,不是我眼前改的那段插件代码,而是 OpenClaw 宿主 dist 里生成 schema 的那套逻辑。
这也是这次最值得记住的一点。
很多系统问题最烦的,不是你找不到方向。而是你以为方向找对了,甚至手也动了,结果真正生效的,根本不是你改的那份。
这时候你如果不去确认“谁在生效”,后面就很容易一直在错误的层面打转。
当然,这里可能也有我自己的问题。
因为前面我改过飞书插件,主要是为了增强发送文件的能力。
所以严格来说,我也不能百分百排除,是不是我前面的改动把这个问题整复杂了。
如果有也在折腾 OpenClaw、或者有在用飞书 plugin 的朋友,欢迎留言说说你有没有遇到过类似问题。
我也想看看,这到底是不是我的锅
夜雨聆风