fix #95291: message tool fails to deliver files/images on Feishu (400 volc-dcdn / write ECONNRESET) while same Lark SDK upload succeeds standalone#95514
Conversation
|
Codex review: needs maintainer review before merge. Reviewed July 17, 2026, 8:53 AM ET / 12:53 UTC. Summary PR surface: Source +85, Tests +34. Total +119 across 2 files. Reproducibility: yes. at source level: current main still routes SDK-shaped Buffer multipart data through the timeout/proxy-aware wrapper without this normalization, and the linked report gives a concrete message-tool file/image path. The supplied live proof also reports the failure disappearing after the patch, although this review did not independently execute a private Feishu tenant. Review metrics: none identified. Root-cause cluster Members:
Proposal only: this assessment does not dispatch repair, suppress jobs, mutate sibling items, close, or merge anything. Merge readiness Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch. Risk before merge
Maintainer options:
Next step before merge
Security Review detailsBest possible solution: Land the narrowly gated wrapper-boundary normalization after refreshing against current main and confirming exact-head checks, preserving the canonical Lark SDK upload methods, timeout/proxy behavior, filenames, scalar multipart fields, and byte-for-byte media contents. Do we have a high-confidence way to reproduce the issue? Yes at source level: current main still routes SDK-shaped Buffer multipart data through the timeout/proxy-aware wrapper without this normalization, and the linked report gives a concrete message-tool file/image path. The supplied live proof also reports the failure disappearing after the patch, although this review did not independently execute a private Feishu tenant. Is this the best way to solve the issue? Yes; adapting the SDK upload payload at the wrapper boundary is narrower and more maintainable than bypassing the SDK or adding downstream fallbacks, and it leaves configuration, routing, retries, and non-upload requests unchanged. AGENTS.md: found and applied where relevant. Codex review notes: model internal, reasoning high; reviewed against 62407b36d780. Label changesLabel changes:
Label justifications:
Evidence reviewedPR surface: Source +85, Tests +34. Total +119 across 2 files. View PR surface stats
What I checked:
Likely related people:
What the crustacean ranks mean
Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics. How this review workflow works
Review history (1 earlier review cycle)
|
|
Complementary observations on top of ClawSweeper's review: The fix to normalize SDK multipart Buffer uploads into native FormData inside the timeout-aware HTTP wrapper is a clean change. Two observations:
|
d673910 to
8a4619b
Compare
|
This pull request has been automatically marked as stale due to inactivity. |
b435447 to
c6bc27f
Compare
Convert Feishu SDK multipart Buffer upload parts into explicit FormData before they reach the wrapped HTTP transport. The SDK upload helpers pass multipart data as a plain object with Buffer media parts; relying on implicit serialization in the timeout/proxy-aware HTTP wrapper was fragile and caused file/image delivery failures (400 volc-dcdn / write ECONNRESET) even though the same credentials succeeded via a standalone SDK upload. Adds normalizeMultipartUploadData, applied on the request path of the shared Feishu HTTP instance, plus a regression test covering the multipart-to-FormData normalization. Rebased onto current origin/main, which replaced the old synchronous injectTimeout path with the async proxy-aware injectRequestOptions flow; the multipart normalization is now applied before that flow.
c6bc27f to
78024f4
Compare
|
ClawSweeper status: review started. I am starting a fresh review of this pull request: fix #95291: message tool fails to deliver files/images on Feishu (400 volc-dcdn / write ECONNRESET) while same Lark SDK upload succeeds standalone This is item 1/1 in the current shard. Shard 0/1. This placeholder means the worker is alive and reading the current context. I will edit this same comment with the actual review when the claws are done clicking. Crustacean status: shell secured, claws on keyboard, evidence pebbles being sorted. |
|
Merged via squash.
|
* origin/main: (24 commits) fix(agents): keep compaction on live session model (#95713) fix(plugin-sdk): guard provider catalog live URL parsing against malformed responses (#109986) chore(cli): drop dead classifiers and single-use wrappers left by fallback removal (#112191) feat(ui): expand the lobster pet's random universe (#112073) chore(ci): audit dependency fingerprint exports (#112190) fix(ui): preserve graphemes in provider icons (#109509) fix(ui): align settings search with navigation rows (#112172) fix(agents): allow configless gateway rebind to activate standalone owner (#111841) fix(secrets): register secret targets for installed-origin plugins (#104347) improve(ui): show real machine identity in the place picker (#112162) fix: webChat scrollback history is too limited — older assistant replies and tool outputs disappear when scrolling… (#104250) test(ui): run DOM-free suites in Node (#112031) feat(nodes): make computer.act eligibility capability-based for desktop nodes (#112107) fix(apps): harden mobile gateway and watch state fix(ci): restore Z.AI API Platform validation (#112171) fix(ui): prevent Logs controls from overlapping (#112170) fix #95291: message tool fails to deliver files/images on Feishu (400 volc-dcdn / write ECONNRESET) while same Lark SDK upload succeeds standalone (#95514) refactor(cli)!: remove automatic gateway→embedded fallback from openclaw agent (#112074) refactor: move provider transports into packages/ai behind a typed host port (#111669) fix(anthropic): complete transcript reverse-scan windows across short reads (#109431) ...
…shu (400 volc-dcdn / write ECONNRESET) while same Lark SDK upload succeeds standalone (openclaw#95514) * fix(feishu): normalize media upload multipart data Convert Feishu SDK multipart Buffer upload parts into explicit FormData before they reach the wrapped HTTP transport. The SDK upload helpers pass multipart data as a plain object with Buffer media parts; relying on implicit serialization in the timeout/proxy-aware HTTP wrapper was fragile and caused file/image delivery failures (400 volc-dcdn / write ECONNRESET) even though the same credentials succeeded via a standalone SDK upload. Adds normalizeMultipartUploadData, applied on the request path of the shared Feishu HTTP instance, plus a regression test covering the multipart-to-FormData normalization. Rebased onto current origin/main, which replaced the old synchronous injectTimeout path with the async proxy-aware injectRequestOptions flow; the multipart normalization is now applied before that flow. * fix(feishu): scope multipart media normalization * test(feishu): mark multipart auth fixture synthetic * test(feishu): use field-shaped secret fixtures * fix(feishu): validate multipart upload endpoints --------- Co-authored-by: Peter Steinberger <[email protected]>
Closes #95291
What Problem This Solves
Fixes an issue where Feishu/Lark users sending local files or images through the OpenClaw
messagetool could see media delivery fail with CDN-edge upload errors, even though the same app credentials and media succeeded through a standalone Lark SDK upload.Why This Change Was Made
OpenClaw intentionally wraps the Lark SDK HTTP instance to inject request timeouts. The SDK upload helpers provide multipart upload data as a plain object containing Buffer media parts; relying on implicit serialization in the wrapped transport path was fragile. This change keeps the canonical SDK upload APIs and normalizes only Feishu media upload requests into explicit native
FormDatabefore delegating to the SDK default HTTP instance.The follow-up type repair also copies each Buffer media part into an
ArrayBuffer-backedUint8Arraybefore constructing the nativeBlob. That preserves the uploaded bytes while satisfying the current TypeScriptBlobPartcontract, which rejectsBuffer<ArrayBufferLike>because it can be backed bySharedArrayBuffer.User Impact
Users can send Feishu
messagemedia attachments from local files/images through OpenClaw without hitting the malformed multipart upload path. The fix applies to both file and image uploads while leaving unrelated Feishu requests unchanged.Evidence
node scripts/run-tsgo.mjs -p tsconfig.extensions.json,node scripts/run-tsgo.mjs -p tsconfig.core.json --incremental --tsBuildInfoFile .artifacts/tsgo-cache/core.tsbuildinfo,node scripts/run-tsgo.mjs -p test/tsconfig/tsconfig.core.test.json --incremental --tsBuildInfoFile .artifacts/tsgo-cache/core-test.tsbuildinfo, andnode scripts/run-tsgo.mjs -p test/tsconfig/tsconfig.extensions.test.json --incremental --tsBuildInfoFile .artifacts/tsgo-cache/extensions-test.tsbuildinfo— passed.check-test-typesfollow-up: the Feishu client regression test now reads the delegated request options from the actualrequestmock, matching the helper's typed input and preserving the runtime assertion.CI=1 node scripts/run-vitest.mjs extensions/feishu/src/client.test.ts extensions/feishu/src/media.test.ts— passed.node scripts/check-extension-package-tsc-boundary.mjs --mode=compile— passed.node scripts/run-oxlint.mjs --tsconfig config/tsconfig/oxlint.extensions.json extensions— passed.node scripts/run-vitest.mjs run --config test/vitest/vitest.tooling.config.ts --reporter=verbose src/scripts/test-projects.test.ts— passed after syncing current main and refreshing dependencies.git diff --check— passed.FormDatawhile preserving timeout injection and headers.Summary
messagemedia delivery for local files and images when the SDK upload goes through OpenClaw's timeout-aware HTTP instance.messagetool could fail before Feishu returned a normal OpenAPI JSON response, producing CDN-edge upload errors even though the same app credentials and media worked through a standalone Lark SDK upload.BlobPartcompatibility repair.im.file.createandim.image.create) plus OpenClaw's existing timeout-aware HTTP wrapper. The Buffer-to-Blob conversion only adapts Node Buffer bytes to the nativeBlobParttype accepted by the wrapper boundary.Root Cause
Content-Type: multipart/form-data. OpenClaw wraps the SDK default HTTP instance to inject timeouts; in that wrapped path, relying on implicit multipart serialization was fragile and caused malformed upload requests to be rejected at the Feishu CDN edge before normal OpenAPI handling. The review follow-up then exposed that passing a NodeBuffer<ArrayBufferLike>directly tonew Blob(...)is not accepted by current TypeScript DOM typings because the Buffer backing store may beSharedArrayBuffer.fileorimagepart, the wrapper now builds an explicit nativeFormDatapayload before delegating to the SDK default HTTP instance, so the SDK upload request has the multipart shape Feishu expects while the canonical SDK methods and timeout behavior remain unchanged. The follow-up copies the Buffer into a freshUint8Array<ArrayBuffer>before creating theBlob, preserving bytes while using a BlobPart that TypeScript and native Blob accept.Real behavior proof
messagemedia-send path delivered file and image messages to both DM and group.BlobParttype repair. This round revalidated the code path with focused Feishu tests plus full relevant type/lint/package-boundary checks after syncing main.Regression Test Plan
node scripts/run-tsgo.mjs -p tsconfig.extensions.json— passed.node scripts/run-tsgo.mjs -p tsconfig.core.json --incremental --tsBuildInfoFile .artifacts/tsgo-cache/core.tsbuildinfo— passed.node scripts/run-tsgo.mjs -p test/tsconfig/tsconfig.core.test.json --incremental --tsBuildInfoFile .artifacts/tsgo-cache/core-test.tsbuildinfo— passed.node scripts/run-tsgo.mjs -p test/tsconfig/tsconfig.extensions.test.json --incremental --tsBuildInfoFile .artifacts/tsgo-cache/extensions-test.tsbuildinfo— passed.CI=1 node scripts/run-vitest.mjs extensions/feishu/src/client.test.ts extensions/feishu/src/media.test.ts— passed.node scripts/check-extension-package-tsc-boundary.mjs --mode=compile— passed.node scripts/run-oxlint.mjs --tsconfig config/tsconfig/oxlint.extensions.json extensions— passed.node scripts/run-vitest.mjs run --config test/vitest/vitest.tooling.config.ts --reporter=verbose src/scripts/test-projects.test.ts— passed.git diff --check— passed.extensions/feishu/src/client.test.ts.filepart is passed through the timeout-aware HTTP wrapper and must be delegated as explicitFormDatarather than the original plain object.Review findings addressed
status: ⏳ waiting on author): addressed by syncing main, applying the requested repair, and revalidating the PR branch. The label itself is expected to clear only after exact-head CI/bot or maintainer re-evaluation.check-prod-types/ extension type checks): fixed.node scripts/run-tsgo.mjs -p tsconfig.extensions.jsonpassed after copying Buffer media parts intoUint8Array<ArrayBuffer>beforenew Blob(...).extensions/feishu/src/client.tsby adding a local Buffer-to-BlobPart adapter before constructing the nativeBlob.node scripts/check-extension-package-tsc-boundary.mjs --mode=compile.CI=1 node scripts/run-vitest.mjs extensions/feishu/src/client.test.ts extensions/feishu/src/media.test.ts.check-test-typesfollow-up: fixed by passingmockBaseHttpInstance.requestto the test helper that reads mock call options;node scripts/run-tsgo.mjs -p test/tsconfig/tsconfig.extensions.test.json --incremental --tsBuildInfoFile .artifacts/tsgo-cache/extensions-test.tsbuildinfonow passes.node scripts/run-oxlint.mjs --tsconfig config/tsconfig/oxlint.extensions.json extensions.Merge risk
FormData/Blob, public contract scan.Blob.