chenhg5/cc-connect · error

%s: CDN upload client error %d: %s

Error message

%s: CDN upload client error %d: %s

What it means

uploadBufferToCDN rejects the upload when the WeChat CDN returns an HTTP 4xx client error. The CDN signals the reason via the x-error-message header (falling back to resp.Status). Client errors are NOT retried because retrying a malformed/rejected request cannot succeed.

Source

Thrown at platform/weixin/cdn.go:203

		req, err := http.NewRequestWithContext(ctx, http.MethodPost, u, bytes.NewReader(ciphertext))
		if err != nil {
			return "", fmt.Errorf("%s: new request: %w", label, err)
		}
		req.Header.Set("Content-Type", "application/octet-stream")
		resp, err := client.Do(req)
		if err != nil {
			lastErr = err
			slog.Warn("weixin: CDN upload request failed", "label", label, "attempt", attempt, "error", err)
			continue
		}
		_, _ = io.Copy(io.Discard, io.LimitReader(resp.Body, 4096))
		_ = resp.Body.Close()
		if resp.StatusCode >= 400 && resp.StatusCode < 500 {
			msg := resp.Header.Get("x-error-message")
			if msg == "" {
				msg = resp.Status
			}
			return "", fmt.Errorf("%s: CDN upload client error %d: %s", label, resp.StatusCode, msg)
		}
		if resp.StatusCode != http.StatusOK {
			msg := resp.Header.Get("x-error-message")
			if msg == "" {
				msg = fmt.Sprintf("status %d", resp.StatusCode)
			}
			lastErr = fmt.Errorf("%s: CDN upload server error: %s", label, msg)
			slog.Warn("weixin: CDN upload server error", "label", label, "attempt", attempt, "error", lastErr)
			continue
		}
		dl := resp.Header.Get("x-encrypted-param")
		if dl == "" {
			lastErr = fmt.Errorf("%s: CDN response missing x-encrypted-param", label)
			slog.Warn("weixin: CDN upload bad response", "label", label, "attempt", attempt)
			continue
		}
		return dl, nil
	}

View on GitHub (pinned to 4000b2338a)

Solutions

  1. Inspect the %s (msg) portion of the error for the CDN's x-error-message to identify the rejection reason
  2. Call getUploadUrl again to get a fresh upload_param/upload_full_url and retry with the new URL
  3. Verify cdn_base (default https://novac2c.cdn.weixin.qq.com/c2c) is correct in config.toml
  4. Check that the aes_key passed to uploadBufferToCDN is exactly 16 bytes and that the filekey matches the getUploadUrl response

Example fix

// before: blind retry on any error
for i := 0; i < 3; i++ { _, err := uploadToWeixinCDN(ctx, ...); if err == nil { break } }
// after: refresh the upload URL only on client-error and retry once
if err != nil && strings.Contains(err.Error(), "CDN upload client error") {
    resp, err2 := client.getUploadURL(ctx, freshGetUploadURLRequest)
    if err2 == nil { /* rebuild URL and retry once */ }
}
Defensive patterns

Strategy: validation

Validate before calling

// before uploading, ensure the upload URL and key are fresh and well-formed
if uploadParam == "" || len(aesKey) != 16 || time.Since(uploadURLIssuedAt) > 5*time.Minute {
    // re-call getUploadUrl and re-derive aesKey
}

Prevention

When it happens

Trigger: POST of AES-ECB ciphertext to the CDN upload URL returns status 400-499; typical causes are an expired/invalid encrypted_query_param or filekey from getUploadUrl, a wrong URL, or the CDN rejecting content metadata.

Common situations: Stale upload URL reused after expiry; filekey mismatch between getUploadUrl and the upload request; CDN base URL misconfigured in config.toml; CDN-side rejecting oversize or malformed payloads with 4xx.

Understand the failure class

Background: "API error: {status}" and "HTTP 401/403/404/429/5xx" errors: non-2xx HTTP responses explained — this error's family across 27 libraries.

Related errors


AI-assisted analysis of chenhg5/cc-connect@4000b2338a (2026-09-06). Data as JSON: /api/errors/bdc9aa3624b6c6ac. Report an issue: GitHub.