fix(cdn): preserve response headers in CDN upload (#149)#160
Open
DraixAgent wants to merge 1 commit into
Open
Conversation
…encent#149) Switch the Weixin CDN upload transport from global fetch to a small node:https / node:http POST helper (postBufferRaw in src/cdn/cdn-transport.ts). This preserves the raw response headers as Node sees them on the wire, so x-encrypted-param survives in the live OpenClaw message-tool execution context. In that context the global fetch implementation was exposing an empty Headers object on 200 OK CDN responses, while the same flow worked from the delivery-recovery path and from standalone Node scripts. The result was live image / file / video sends failing immediately with "CDN upload response missing x-encrypted-param header". The new transport is its own module so tests can mock it cleanly, and so the retry/status/header-parsing logic in cdn-upload.ts stays unchanged. Fixes Tencent#149
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Switches the Weixin CDN upload transport from global
fetchto a smallnode:https/node:httpPOST helper (postBufferRawinsrc/cdn/cdn-transport.ts). This preserves the raw response headers as Node sees them on the wire, sox-encrypted-paramsurvives in the live OpenClawmessage-tool execution context.Fixes #149
Problem
When sending image / file / video media via the live OpenClaw
messagetool path, the Weixin CDN POST returned HTTP 200 butglobal fetchexposed an emptyHeadersobject, sosrc/cdn/cdn-upload.tscouldn't readx-encrypted-paramand the send failed with:The same CDN flow worked correctly from:
which strongly suggested the issue was something about how
global fetchwas wired in the live tool execution context, not the CDN itself.Root-cause investigation
Before changing the transport I looked for the actual source of the empty-
Headersview in this repo:grepforsetGlobalDispatcher,MockAgent,ProxyAgent,HttpsProxyAgent,globalThis.fetch,global.fetch, customAgent/interceptor/dispatcherconfiguration → nothing in this plugin overrides globalfetchor installs a custom undici dispatcher.Walked both call sites that reach
uploadBufferToCdn:channel.ts→messaging/send-media.ts→cdn/upload.ts→cdn/cdn-upload.tssendWeixinMediaFileentry point, also routes throughcdn/cdn-upload.tsBoth paths converge on the same function, so the difference can't be inside this plugin's code — it has to be in whatever runtime/interceptor is active around the live
message-tool path (likely on the OpenClaw core side, outside this repo).Given the symptom (response status visible, response headers empty) and that the recovery + standalone flows both work, the most plausible cause is something in the host runtime (a wrapped
fetch, a logging/observability interceptor, or an undici dispatcher) stripping or rewriting the response headers view for that one execution context. Locating and fixing that in OpenClaw core is out of scope for this plugin PR.So this PR implements the workaround the reporter validated: use Node's built-in HTTPS transport for the CDN POST only, which sees the raw on-the-wire headers regardless of what's wrapped around
fetchin the host process. The rest of the plugin (which calls non-CDN APIs) continues to usefetch.Fix
src/cdn/cdn-transport.tsexportspostBufferRaw(url, body)which POSTs a buffer vianode:https(ornode:httpforhttp://URLs) and returns{ status, getHeader, text }— a minimal, case-insensitive view over Node's raw response.src/cdn/cdn-upload.tsswaps the singlefetch(...)call insideuploadBufferToCdnforpostBufferRaw(...). All retry / status-class / error-handling / header-parsing logic is unchanged. Public API ofuploadBufferToCdnis unchanged.Affected files:
src/cdn/cdn-transport.ts(new)src/cdn/cdn-upload.tssrc/cdn/cdn-upload.test.tssrc/cdn/upload.test.tsTests
Existing CDN tests are preserved (rewritten in
upload.test.tsto mockpostBufferRawinstead offetch, sincecdn-upload.tsno longer touchesfetch). New tests cover the bug directly and use a real loopbacknode:httpserver, not just mocks — mockingfetchcannot catch the regression in #149 because the bug is in the transport layer, not the call site.Coverage added in
src/cdn/cdn-upload.test.ts:x-encrypted-param; the new transport surfaces the header correctly.x-error-messageon every retry; we propagate the server message (e.g."cdn unavailable"), not the misleading"x-encrypted-param missing"wording.Plus regression coverage for the transport itself:
postBufferRawpreserves response headers from a real Node HTTP server (the actual regression assertion for CDN upload via global fetch exposes empty headers in live message-tool path #149), including case-insensitive lookup.postBufferRawsends body bytes andContent-Type/Content-Lengthexactly as expected.postBufferRawrejects unsupported protocols.Plus preserved behavioral tests on
uploadBufferToCdn(retries on 5xx, immediate fail on 4xx, missing header after all retries,x-error-messagepriority for 4xx, missing URL params guard).Total: 15 tests in
cdn-upload.test.ts(was 8), 10 tests inupload.test.ts(was 8), all passing locally.Validation
Full suite: 1 pre-existing unrelated failure in
src/auth/pairing.test.tsonupstream/main(confirmed by running the same test on a clean checkout) — out of scope for this PR.Notes
fetchfor everything except the CDN POST.pic-decrypt.ts(CDN downloads) doesn't depend on response headers in this way, andapi.tstalks to the Weixin JSON API, which is unaffected by the observed bug.fetchresponse headers ever gets identified and fixed, this transport can keep coexisting safely; it doesn't change the wire protocol, just sidesteps the broken view.