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

  1. 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.
  2. 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.
  3. 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.
  4. If the image Buffer is empty or truncated upstream, validate imageData.length > 0 before calling sendImage.
  5. 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

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


AI-assisted analysis of CherryHQ/cherry-studio@726446b54c (2026-08-12). Data as JSON: /api/errors/989f45c0f97d744d. Report an issue: GitHub.