message tool fails to deliver files/images on Feishu (400 volc-dcdn / write ECONNRESET) while the same Lark SDK upload succeeds standalone
Summary
Using the message tool with action=send + a local file (filePath / media) on the Feishu channel fails reliably:
- Files (
.pptx, .pdf, …) → Request failed with status code 400
- Images (
.png) → write ECONNRESET
The 400 body is not a Feishu OpenAPI JSON error — it is an HTML edge-error page: 400 Bad Request ... Powered by volc-dcdn (the CDN in front of the upload endpoint). The request is being rejected at the edge before reaching the Feishu API.
A standalone Node script using the same bundled @larksuiteoapi/node-sdk on the same host / same app credentials / same file uploads (im.file.create / im.image.create) and sends the file message successfully, every time. So the file, credentials, network egress, and Feishu app permissions are all fine — the defect is in OpenClaw's long-running outbound media pipeline for Feishu.
Environment
- openclaw: 2026.6.6
@larksuiteoapi/node-sdk: 1.60.0 (extensions) / 1.66.1 (lark project)
- Node: v22.22.0, Linux x64
- Channel: Feishu (Lark), bot identity, DM + group both affected
Repro
Observed (gateway log):
[tools] message failed: Request failed with status code 400
raw_params={"action":"send","filePath":".../eval-summit-onepage.pptx", ...}
[tools] message failed: write ECONNRESET
raw_params={"action":"send","media":".../eval-summit-onepage.png", ...}
Full 400 body retrieved on retry: an HTML page ending with Powered by volc-dcdn.
Deterministic and type/size-independent: a 74-byte .txt sent via the message tool also returns 400 on the current running 2026.6.6 gateway, while the standalone SDK script sends that same file successfully. This rules out file size, file type, and multipart body size as the trigger — the outbound media path is broken for all files.
Key diagnostic signals
- The thrown error is a raw axios error (
Request failed with status code 400) with no Feishu file upload failed prefix. That wrapper is applied by uploadFileFeishu → requestFeishuApi(() => client.im.file.create(...), "Feishu file upload failed", ...). Its absence suggests the failing request is not going through the SDK's im.file.create wrapper — i.e. the outbound media path constructs/sends a request differently than the verified SDK path.
- Edge rejection, not API rejection.
volc-dcdn returning a 400 HTML page is characteristic of a malformed multipart upload (bad boundary / Content-Length / chunked-vs-buffered body), rejected before the Feishu API sees it.
- Process-scoped, not config-scoped. Same SDK + same creds + same file in a one-shot script → success. Long-running gateway → fail. Restarting the gateway does not fix it. This points at the gateway's outbound/HTTP layer (global dispatcher/agent, an undici/fetch interceptor, or how the media pipeline streams the multipart body) rather than credentials or transient network.
Expected
message tool file/image delivery on Feishu should route through (or behave identically to) the verified im.file.create / im.image.create SDK path and succeed, as the standalone SDK call does.
Workaround (current)
We bypass the message tool and call the Lark SDK directly (upload → send file/image message). This is reliable across SDK versions and buffer/stream inputs.
Offer
I can share the minimal standalone repro script (SDK upload+send that succeeds) and the captured volc-dcdn 400 HTML body, and can point at the exact outbound code path on request.
messagetool fails to deliver files/images on Feishu (400 volc-dcdn / write ECONNRESET) while the same Lark SDK upload succeeds standaloneSummary
Using the
messagetool withaction=send+ a local file (filePath/media) on the Feishu channel fails reliably:.pptx,.pdf, …) →Request failed with status code 400.png) →write ECONNRESETThe 400 body is not a Feishu OpenAPI JSON error — it is an HTML edge-error page:
400 Bad Request ... Powered by volc-dcdn(the CDN in front of the upload endpoint). The request is being rejected at the edge before reaching the Feishu API.A standalone Node script using the same bundled
@larksuiteoapi/node-sdkon the same host / same app credentials / same file uploads (im.file.create/im.image.create) and sends the file message successfully, every time. So the file, credentials, network egress, and Feishu app permissions are all fine — the defect is in OpenClaw's long-running outbound media pipeline for Feishu.Environment
@larksuiteoapi/node-sdk: 1.60.0 (extensions) / 1.66.1 (lark project)Repro
Observed (gateway log):
Full 400 body retrieved on retry: an HTML page ending with
Powered by volc-dcdn.Deterministic and type/size-independent: a 74-byte
.txtsent via themessagetool also returns 400 on the current running 2026.6.6 gateway, while the standalone SDK script sends that same file successfully. This rules out file size, file type, and multipart body size as the trigger — the outbound media path is broken for all files.Key diagnostic signals
Request failed with status code 400) with noFeishu file upload failedprefix. That wrapper is applied byuploadFileFeishu→requestFeishuApi(() => client.im.file.create(...), "Feishu file upload failed", ...). Its absence suggests the failing request is not going through the SDK'sim.file.createwrapper — i.e. the outbound media path constructs/sends a request differently than the verified SDK path.volc-dcdnreturning a 400 HTML page is characteristic of a malformed multipart upload (bad boundary / Content-Length / chunked-vs-buffered body), rejected before the Feishu API sees it.Expected
messagetool file/image delivery on Feishu should route through (or behave identically to) the verifiedim.file.create/im.image.createSDK path and succeed, as the standalone SDK call does.Workaround (current)
We bypass the
messagetool and call the Lark SDK directly (upload → sendfile/imagemessage). This is reliable across SDK versions and buffer/stream inputs.Offer
I can share the minimal standalone repro script (SDK upload+send that succeeds) and the captured volc-dcdn 400 HTML body, and can point at the exact outbound code path on request.