chenhg5/cc-connect · error

%s: CDN upload failed after %d attempts: %w

Error message

%s: CDN upload failed after %d attempts: %w

What it means

Final error returned by uploadBufferToCDN after exhausting cdnUploadMaxRetries (3) attempts. It wraps the last underlying failure (lastErr) — a transport error, 5xx server error, or missing x-encrypted-param — so the root cause appears after 'attempts:'.

Source

Thrown at platform/weixin/cdn.go:223

		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
	}
	if lastErr != nil {
		return "", fmt.Errorf("%s: CDN upload failed after %d attempts: %w", label, cdnUploadMaxRetries, lastErr)
	}
	return "", fmt.Errorf("%s: CDN upload failed after %d attempts", label, cdnUploadMaxRetries)
}

func md5Hex(b []byte) string {
	h := md5.Sum(b)
	return hex.EncodeToString(h[:])
}

func detectImageMime(b []byte) string {
	if len(b) >= 3 && b[0] == 0xFF && b[1] == 0xD8 && b[2] == 0xFF {
		return "image/jpeg"
	}
	if len(b) >= 8 && string(b[0:8]) == "\x89PNG\r\n\x1a\n" {
		return "image/png"
	}
	if len(b) >= 6 && (string(b[0:6]) == "GIF87a" || string(b[0:6]) == "GIF89a") {
		return "image/gif"

View on GitHub (pinned to 4000b2338a)

Solutions

  1. Read the wrapped %w cause after 'attempts:' to see the actual failure (transport vs server error vs missing header)
  2. If it's a network error, check connectivity/DNS/proxy to the CDN host and retry later
  3. If it's 'server error', wait for the CDN side to recover or add caller-level backoff with longer delays
  4. If it's 'missing x-encrypted-param' on every attempt, verify cdn_base configuration and API-version compatibility

Example fix

// before: no caller backoff
err := uploadToWeixinCDN(ctx, data, key)
// after: exponential backoff around the 3 built-in attempts
var err error
for d := time.Second; d <= 8*time.Second; d *= 2 {
    if err = uploadToWeixinCDN(ctx, data, key); err == nil { break }
    time.Sleep(d)
}
Defensive patterns

Strategy: retry

Validate before calling

// pre-flight network sanity check before large uploads
if _, err := net.LookupHost("novac2c.cdn.weixin.qq.com"); err != nil { /* postpone upload */ }

Try / catch

var err error
for d := time.Second; d <= 8*time.Second; d *= 2 {
    if err = uploadToWeixinCDN(ctx, data, key); err == nil { break }
    time.Sleep(d)
}
if err != nil { log.Printf("upload permanently failed: %v", err) }

Prevention

When it happens

Trigger: All 3 upload attempts failed with retryable errors: client.Do transport failures, HTTP >= 500, or 200 responses missing x-encrypted-param.

Common situations: Sustained CDN outage; network partition between the bot host and novac2c.cdn.weixin.qq.com; persistent response-contract mismatch (every 200 lacks the header).

Understand the failure class

Background: "API request failed": what wrapped HTTP errors from external APIs mean and how to find the real cause — this error's family across 29 libraries.

Related errors


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