CherryHQ/cherry-studio · error · Error
Failed to upload image to WeChat CDN
Error message
Failed to upload image to WeChat CDN
What it means
Thrown by WeixinBot.sendImage() after cdnUploadImage() returns null. The upload pipeline first requests an upload URL from the WeChat /ilink/bot/getuploadurl endpoint, then AES-ECB-encrypts the image and POSTs it to the WeChat CDN. A null return means either the CDN responded with a non-2xx HTTP status, or the response was missing the required x-encrypted-param download header. The error message intentionally hides the underlying HTTP status to avoid leaking CDN internals.
Source
Thrown at src/main/ai/channels/adapters/wechat/WeChatProtocol.ts:819
} catch (error) {
logger.error('Failed to download WeChat file', error instanceof Error ? error : { error: String(error) })
return null
}
}
/**
* Send an image to a user by uploading to WeChat CDN.
*/
async sendImage(userId: string, imageData: Buffer): Promise<void> {
const contextToken = this.contextTokens.get(userId)
if (!contextToken) {
logger.warn('No cached context token for sendImage, sending without context', { userId })
}
const credentials = await this.ensureCredentials()
const uploaded = await cdnUploadImage(this.baseUrl, credentials.token, this.uin, userId, imageData)
if (!uploaded) {
throw new Error('Failed to upload image to WeChat CDN')
}
const msg = {
from_user_id: '',
to_user_id: userId,
client_id: randomUUID(),
message_type: MessageType.BOT,
message_state: MessageState.FINISH,
context_token: contextToken ?? '',
item_list: [
{
type: MessageItemType.IMAGE,
image_item: {
media: {
encrypt_query_param: uploaded.downloadEncryptedQueryParam,
aes_key: Buffer.from(uploaded.aeskey).toString('base64'),
encrypt_type: 1
},View on GitHub (pinned to 726446b54c)
Solutions
- Check whether the WeChat CDN endpoint is reachable from the host (network/proxy/VPN) and retry — transient CDN 5xx or timeouts are the most common cause.
- Verify credentials are still valid by calling ensureCredentials() before sendImage; an expired token can cause the getuploadurl step to return an upload_param that the CDN rejects.
- Inspect the logger.error output at the line just before the throw — cdnUploadImage logs either 'CDN upload failed' with the HTTP status, or 'CDN upload response missing x-encrypted-param header', which pinpoints the exact failure branch.
- If the image Buffer is empty or truncated upstream, validate imageData.length > 0 before calling sendImage.
- If running behind a proxy, ensure the proxy does not strip or rewrite the x-encrypted-param response header from the CDN.
Example fix
// before
await this.bot.sendImage(chatId, Buffer.from(file.data, 'base64'))
// after — guard empty buffer and surface the CDN failure branch
const buf = Buffer.from(file.data, 'base64')
if (buf.length === 0) throw new Error('Cannot send an empty image to WeChat')
await this.bot.sendImage(chatId, buf) Defensive patterns
Strategy: try-catch
Validate before calling
// Validate image data before calling sendImage
if (!imageData || imageData.length === 0) {
throw new Error('Image data is empty — cannot upload to WeChat CDN')
}
// Optionally check a reasonable size ceiling
if (imageData.length > 20 * 1024 * 1024) {
logger.warn('Large image may be rejected by WeChat CDN', { size: imageData.length })
} Try / catch
try {
await this.bot.sendImage(chatId, imageData)
} catch (error) {
if (error instanceof Error && error.message === 'Failed to upload image to WeChat CDN') {
logger.error('WeChat CDN upload failed, falling back to text notification', { chatId })
await this.bot.reply({ userId: chatId, _contextToken: '' }, '[Image delivery failed]')
} else {
throw error
}
} Prevention
- Ensure credentials are fresh by calling ensureCredentials() immediately before sendImage.
- Verify network connectivity to the WeChat CDN endpoint before attempting uploads.
- Check that the proxy configuration does not strip the x-encrypted-param response header.
- Log the HTTP status from cdnUploadImage failures (it already logs via logger.error) to distinguish network errors from auth errors.
When it happens
Trigger: Called from WeChatAdapter.sendFile() which decodes a base64 FileAttachment and passes it to bot.sendImage(chatId, buffer). Fails when: (1) the CDN upload POST at CDN_BASE_URL/upload returns HTTP >= 400 (line 341-343), or (2) the 200 response lacks the x-encrypted-param response header (line 346-350), or (3) the upstream getuploadurl call itself fails (which would throw before reaching the null check). Can also fire transiently under proxy misconfiguration or network disruption.
Common situations: WeChat CDN is temporarily unreachable or rate-limiting the bot account; the session token expired mid-upload (cdnUploadImage uses credentials.token which may have rotated since ensureCredentials); a corporate proxy or firewall strips the x-encrypted-param response header; the image data is corrupt or zero-length causing the CDN to reject it; the WeChat bot account has insufficient media-upload quota.
Related errors
- Rerank response results must contain numeric index and relev
- Failed to get gateway URL: HTTP ${response.status} - ${error
- ${label} returned non-JSON (HTTP ${response.status})
- ${label} failed with HTTP ${response.status}
- Message text cannot be empty.
AI-assisted analysis of CherryHQ/cherry-studio@726446b54c (2026-08-12).
Data as JSON: /api/errors/989f45c0f97d744d.
Report an issue: GitHub.