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
- 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
- Use a normal browser-like User-Agent (already set) and consider a Referer header for hotlink-protected hosts
- Verify the image URL directly with curl -I from the server
- 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
- Skip 404 images permanently; retry only 5xx with backoff
- Back off on 429 rate limits from CDNs
- Send browser-like User-Agent/Referer for hotlink-protected hosts
- Monitor which image hosts frequently return 403/404
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.