{"record":{"id":"981608f5a6ef4a2c","repo":"siyuan-note/siyuan","slug":"download-generated-image-failed-with-status-d","errorCode":null,"errorMessage":"download generated image failed with status %d","messagePattern":"download generated image failed with status (.+?)","errorType":"http","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/util/openai.go","lineNumber":799,"sourceCode":"\tparsed, err := url.Parse(rawURL)\n\tif err != nil || parsed.Scheme != \"https\" || parsed.Host == \"\" {\n\t\treturn nil, errors.New(\"generated image URL must use HTTPS\")\n\t}\n\tif err = CheckHostSSRF(parsed.Hostname()); err != nil {\n\t\treturn nil, err\n\t}\n\tclient := generatedImageHTTPClient()\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, rawURL, nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer resp.Body.Close()\n\tif resp.StatusCode < 200 || resp.StatusCode >= 300 {\n\t\treturn nil, fmt.Errorf(\"download generated image failed with status %d\", resp.StatusCode)\n\t}\n\tif resp.ContentLength > maxGeneratedImageBytes {\n\t\treturn nil, errors.New(\"generated image exceeds size limit\")\n\t}\n\tdata, err := io.ReadAll(io.LimitReader(resp.Body, maxGeneratedImageBytes+1))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif len(data) > maxGeneratedImageBytes {\n\t\treturn nil, errors.New(\"generated image exceeds size limit\")\n\t}\n\treturn data, nil\n}\n\nfunc generatedImageHTTPClient() *http.Client {\n\treturn &http.Client{\n\t\tTransport: &http.Transport{\n\t\t\tProxy:       http.ProxyFromEnvironment,","sourceCodeStart":781,"sourceCodeEnd":817,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/251596fc0de2f9528c00c224252fd073a99973f4/kernel/util/openai.go#L781-L817","documentation":"Thrown by downloadGeneratedImage (kernel/util/openai.go:799) when the HTTP response status code is outside the 2xx range. It fires after the SSRF and redirect guards pass, so it specifically indicates the remote image host refused or failed the GET.","triggerScenarios":"GET against the model-returned URL returns 403 (signed URL expired), 404 (object gone), 429 (rate limited), or 5xx (CDN/provider error). Generated-image URLs from providers like OpenAI are typically short-lived signed links, so 403 after a delay is the classic case.","commonSituations":"The download started after the signed URL's TTL expired (slow model + slow pipeline); CDN outage; rate-limit burst; provider revoked the URL; redirect chain that ended at an error page.","solutions":["Retry the download immediately — most 4xx/5xx from signed CDN URLs are transient.","If persistently 403, request b64_json instead of URL delivery so there is no TTL window.","Shorten the gap between CreateImage and the download (avoid long work between them).","Check provider status and rate limits if 429/5xx repeats."],"exampleFix":"// before\ndata, err = downloadGeneratedImage(requestCtx, result.URL)\nif err != nil { return GeneratedImage{}, err }\n\n// after (bounded retry on transient download failures)\nvar data []byte\nfor attempt := 1; attempt <= 3; attempt++ {\n    data, err = downloadGeneratedImage(requestCtx, result.URL)\n    if err == nil { break }\n    logging.LogWarnf(\"image download attempt %d failed: %s\", attempt, err)\n    if attempt < 3 { time.Sleep(time.Duration(attempt) * time.Second) }\n}\nif err != nil { return GeneratedImage{}, err }","handlingStrategy":"retry","validationCode":"// Cannot pre-check a remote status code; pre-flight belongs to retry logic — see tryCatchPattern.","typeGuard":null,"tryCatchPattern":"var data []byte\nfor attempt := 1; attempt <= 3; attempt++ {\n    var derr error\n    data, derr = downloadGeneratedImage(ctx, result.URL)\n    if derr == nil { break }\n    if strings.Contains(derr.Error(), \"failed with status\") && attempt < 3 {\n        time.Sleep(time.Duration(attempt) * time.Second)\n        continue\n    }\n    return GeneratedImage{}, derr\n}","preventionTips":["Minimize the gap between CreateImage and the download — signed URLs expire.","Default to b64_json for short-TTL providers.","Watch for 429 in logs and back off the request rate."],"tags":["openai","image-generation","network","retry","go"],"backgroundTag":null,"analyzedSha":"251596fc0de2f9528c00c224252fd073a99973f4","analyzedAt":"2026-08-12T21:18:37.123Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}