{"record":{"id":"ea7d46f8f08a4d4d","repo":"chenhg5/cc-connect","slug":"remote-image-http-status-d","errorCode":null,"errorMessage":"remote image HTTP status %d","messagePattern":"remote image HTTP status (.+?)","errorType":"http","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"platform/feishu/feishu.go","lineNumber":6622,"sourceCode":"\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, rawURL, nil)\n\tif err != nil {\n\t\treturn nil, \"\", err\n\t}\n\treq.Header.Set(\"User-Agent\", \"cc-connect-feishu-rich-card-image-resolver/1.0\")\n\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn nil, \"\", err\n\t}\n\tdefer func() {\n\t\tif err := resp.Body.Close(); err != nil {\n\t\t\tslog.Debug(\"feishu: close rich card image response body\", \"error\", err)\n\t\t}\n\t}()\n\n\tif resp.StatusCode < 200 || resp.StatusCode >= 300 {\n\t\treturn nil, \"\", fmt.Errorf(\"remote image HTTP status %d\", resp.StatusCode)\n\t}\n\tif resp.ContentLength > richCardImageMaxBytes {\n\t\treturn nil, \"\", fmt.Errorf(\"remote image too large: %d bytes\", resp.ContentLength)\n\t}\n\n\tdata, err := io.ReadAll(io.LimitReader(resp.Body, richCardImageMaxBytes+1))\n\tif err != nil {\n\t\treturn nil, \"\", err\n\t}\n\tif len(data) > richCardImageMaxBytes {\n\t\treturn nil, \"\", fmt.Errorf(\"remote image exceeds %d bytes\", richCardImageMaxBytes)\n\t}\n\tmimeType := http.DetectContentType(data)\n\tif !isSupportedRichCardImageMIME(mimeType) {\n\t\treturn nil, \"\", fmt.Errorf(\"unsupported remote image MIME %q\", mimeType)\n\t}\n\treturn data, mimeType, nil\n}","sourceCodeStart":6604,"sourceCodeEnd":6640,"githubUrl":"https://github.com/chenhg5/cc-connect/blob/4000b2338aa6e850c99df54f8b0ed6ed7460b401/platform/feishu/feishu.go#L6604-L6640","documentation":"When fetching a remote image for a rich (interactive) card, the HTTP response status was outside 2xx. The download is aborted because the body won't contain a usable image. SSRF-safe fetching (custom dialer) still allows any public host, so remote servers can 404/403.","triggerScenarios":"Downloading an image URL referenced by a card and the remote server returns 404 (image moved/deleted), 403 (hotlink protection/auth), 5xx, or a redirect loop ending in non-2xx.","commonSituations":"Expired CDN links; images behind auth tokens that expired; hotlink protection blocking the bot's user agent; DNS pointing at a server that returns errors.","solutions":["Verify the image URL is publicly reachable (curl -I the URL)","Re-generate/re-sign the image URL if it contains an expiring token","Add a standard User-Agent if the remote host blocks non-browser clients","Implement a fallback: catch this error and send the card without the image or with a placeholder","Cache images so transient 5xx don't break card rendering"],"exampleFix":"// caller\nimg, mime, err := fetchRemoteImage(ctx, url)\nif err != nil {\n    slog.Warn(\"image fetch failed, using placeholder\", \"err\", err)\n    img, mime = placeholderImage, \"image/png\"\n}","handlingStrategy":"fallback","validationCode":"resp, err := http.Head(imageURL); if err != nil || resp.StatusCode < 200 || resp.StatusCode >= 300 { /* use fallback image */ }","typeGuard":null,"tryCatchPattern":"img, mime, err := fetchRemoteImage(ctx, url)\nif err != nil {\n    slog.Warn(\"remote image unavailable, using placeholder\", \"err\", err)\n    img, mime = placeholderPNG, \"image/png\"\n}","preventionTips":["Use stable, public, long-lived image URLs","Send a browser-like User-Agent","Avoid URLs behind expiring auth tokens"],"tags":["http","image","network","feishu"],"backgroundTag":"http-non-2xx-response","analyzedSha":"4000b2338aa6e850c99df54f8b0ed6ed7460b401","analyzedAt":"2026-09-06T11:45:09.575Z","contentChangedAt":"2026-09-06T11:45:09.575Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}