Tencent/WeKnora · error

download failed: status=%d

Error message

download failed: status=%d

What it means

downloadFromURL treats any non-200 HTTP status from the file host as a failure and returns 'download failed: status=%d' with the actual status code, closing the response body. WeCom media URLs can expire or the media can be deleted, producing 403/404. The response body is discarded, so retry logic must rely on the status code alone.

Source

Thrown at internal/im/wecom/webhook_adapter.go:588

	if !isAllowedIMAPIHost(rawURL, extraAllowedHost) {
		if err := secutils.ValidateURLForSSRF(rawURL); err != nil {
			return nil, "", fmt.Errorf("URL rejected for security reasons: %v", err)
		}
	}

	req, err := http.NewRequestWithContext(ctx, http.MethodGet, rawURL, nil)
	if err != nil {
		return nil, "", fmt.Errorf("create request: %w", err)
	}

	resp, err := httpClient.Do(req)
	if err != nil {
		return nil, "", fmt.Errorf("download: %w", err)
	}

	if resp.StatusCode != http.StatusOK {
		resp.Body.Close()
		return nil, "", fmt.Errorf("download failed: status=%d", resp.StatusCode)
	}

	logger.Debugf(ctx, "[WeCom] Download response: status=%d content-type=%s content-disposition=%s",
		resp.StatusCode, resp.Header.Get("Content-Type"), resp.Header.Get("Content-Disposition"))

	// Try to extract filename from Content-Disposition header.
	// Supports both standard filename and RFC 5987 filename* parameters.
	if cd := resp.Header.Get("Content-Disposition"); cd != "" {
		if _, params, err := mime.ParseMediaType(cd); err == nil {
			// Prefer filename* (RFC 5987, already decoded by mime.ParseMediaType)
			if fn := params["filename"]; fn != "" {
				fileName = fn
			}
		} else {
			// Fallback: manual extraction for malformed headers
			if idx := strings.Index(cd, "filename="); idx >= 0 {
				extracted := strings.Trim(cd[idx+len("filename="):], "\" ")
				if extracted != "" {

View on GitHub (pinned to 988cbb0330)

Solutions

  1. Capture the status code from the error string and retry only on 5xx/429 with backoff — 403/404 mean the media is gone
  2. For expired temporary media, ask the user to resend or fetch media promptly on message receipt
  3. Ensure the WeCom access_token used for GetMedia is fresh (refresh on 40014/42001) before blaming the URL
  4. Log the URL host and status to identify CDN-side rate limiting vs expiry

Example fix

// before: blind retry
for { rc, _, err := downloadFromURL(ctx, url, name, host); if err == nil { break } }
// after: retry only transient statuses
if strings.Contains(err.Error(), "status=500") || strings.Contains(err.Error(), "status=429") {
    time.Sleep(backoff); continue
}
break // non-retryable
Defensive patterns

Strategy: retry

Try / catch

rc, name, err := adapter.DownloadFile(ctx, msg)
if err != nil && strings.Contains(err.Error(), "download failed: status=") {
    status := parseStatus(err)
    if status >= 500 || status == 429 {
        return retryWithBackoff(ctx) // transient
    }
    return err // 403/404: media expired or deleted, do not retry
}

Prevention

When it happens

Trigger: Downloading a temporary media URL after its expiry (WeCom temporary media expires ~3 days); media deleted by the user; rate limiting (429) from the media CDN; transient 5xx from WeCom servers.

Common situations: Queued jobs retrying long after the message arrived and the media URL expired; fetching MediaId through GetMedia with an expired access_token (yields 4xx); bursts of downloads hitting CDN rate limits.

Related errors


AI-assisted analysis of Tencent/WeKnora@988cbb0330 (2026-09-02). Data as JSON: /api/errors/96e6cb0ea0450a1d. Report an issue: GitHub.