Tencent/WeKnora · error

unexpected status %d

Error message

unexpected status %d

What it means

This error indicates the remote image server responded with an HTTP status other than 200 OK. The status code is embedded in the message so the caller can distinguish 403/404 (bad or protected URL) from 5xx (server-side failure).

Source

Thrown at internal/infrastructure/docparser/image_resolver.go:1022

// downloadImage fetches an image from remoteURL using the provided SSRF-safe
// client. It validates Content-Type and enforces maxRemoteImageSize.
func downloadImage(ctx context.Context, client *http.Client, remoteURL string) (data []byte, mimeType string, err error) {
	req, err := http.NewRequestWithContext(ctx, http.MethodGet, remoteURL, nil)
	if err != nil {
		return nil, "", fmt.Errorf("create request: %w", err)
	}
	// Some CDNs require a browser-like User-Agent.
	req.Header.Set("User-Agent", "Mozilla/5.0 (compatible; WeKnora/1.0)")

	resp, err := client.Do(req)
	if err != nil {
		return nil, "", fmt.Errorf("HTTP GET: %w", err)
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		return nil, "", fmt.Errorf("unexpected status %d", resp.StatusCode)
	}

	// Determine MIME type from Content-Type header.
	ct := resp.Header.Get("Content-Type")
	mimeType, _, _ = mime.ParseMediaType(ct)
	if mimeType == "" {
		mimeType = "application/octet-stream"
	}

	// Only allow image content types (or octet-stream which we sniff later).
	if !strings.HasPrefix(mimeType, "image/") && mimeType != "application/octet-stream" {
		return nil, "", fmt.Errorf("non-image content type: %s", mimeType)
	}

	// Read body with size limit.
	limited := io.LimitReader(resp.Body, maxRemoteImageSize+1)
	body, err := io.ReadAll(limited)
	if err != nil {

View on GitHub (pinned to 988cbb0330)

Solutions

  1. Read the status code: 404 means the image is gone — skip it; 403 means hotlink protection or IP block; 429 means back off and retry later
  2. Use a normal browser-like User-Agent (already set) and consider a Referer header for hotlink-protected hosts
  3. Verify the image URL directly with curl -I from the server
  4. Treat 5xx as transient and retry with backoff

Example fix

// before
if resp.StatusCode != http.StatusOK {
    return nil, "", fmt.Errorf("unexpected status %d", resp.StatusCode)
}
// after
if resp.StatusCode == http.StatusNotFound {
    return nil, "", errRemoteImageNotFound // sentinel callers can skip
}
if resp.StatusCode != http.StatusOK {
    return nil, "", fmt.Errorf("unexpected status %d", resp.StatusCode)
}
Defensive patterns

Strategy: fallback

Validate before calling

// preflight the image URL
resp, err := http.Head(imgURL)
if err != nil || resp.StatusCode != 200 {
    status := 0
    if resp != nil { status = resp.StatusCode }
    return fmt.Errorf("image not fetchable: status=%d err=%v", status, err)
}

Try / catch

data, mimeType, err := downloadImage(ctx, client, remoteURL)
if err != nil {
    if strings.Contains(err.Error(), "unexpected status 404") {
        return nil, "", errRemoteImageNotFound // skip permanently missing images
    }
    if strings.Contains(err.Error(), "unexpected status 4") {
        return nil, "", err // hotlink/forbidden: do not retry
    }
    return nil, "", err // 5xx: caller may retry
}

Prevention

When it happens

Trigger: downloadImage receives a non-200 response: 404 for removed images, 403 for hotlink-protected resources, 429 rate limiting, 5xx from the image host.

Common situations: Hotlink protection rejecting the server's IP or Referer, expired/dead image links in documents, CDN rate limits, geographically blocked hosts.

Related errors


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