{"record":{"id":"a1a9016eedc1ae7f","repo":"zeroclaw-labs/zeroclaw","slug":"generated-image-download-failed-with-http","errorCode":null,"errorMessage":"Generated image download failed with HTTP {}","messagePattern":"Generated image download failed with HTTP (.+?)","errorType":"http","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/zeroclaw-tools/src/image_gen.rs","lineNumber":284,"sourceCode":"                .await\n                .context(\"Failed to download generated image\")?;\n\n            if response.status().is_redirection() {\n                if redirect_count == MAX_IMAGE_REDIRECTS {\n                    anyhow::bail!(\"Too many generated image redirects (max {MAX_IMAGE_REDIRECTS})\");\n                }\n                let location = response\n                    .headers()\n                    .get(LOCATION)\n                    .ok_or_else(|| anyhow::Error::msg(\"Generated image redirect omitted Location\"))?\n                    .to_str()\n                    .context(\"Generated image redirect Location is not valid text\")?;\n                current_url = resolve_redirect_url(&target.url, location)?;\n                continue;\n            }\n\n            if !response.status().is_success() {\n                anyhow::bail!(\n                    \"Generated image download failed with HTTP {}\",\n                    response.status()\n                );\n            }\n\n            return read_generated_image_body(response).await;\n        }\n\n        unreachable!(\"redirect loop exits through success or redirect limit\")\n    }\n\n    /// Read an API key from the environment.\n    fn read_api_key(env_var: &str) -> Result<String, String> {\n        std::env::var(env_var)\n            .map(|v| v.trim().to_string())\n            .ok()\n            .filter(|v| !v.is_empty())\n            .ok_or_else(|| format!(\"Missing API key: set the {env_var} environment variable\"))","sourceCodeStart":266,"sourceCodeEnd":302,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-tools/src/image_gen.rs#L266-L302","documentation":"Bailed when the final (non-redirect) response of the generated-image download is not 2xx (crates/zeroclaw-tools/src/image_gen.rs:283-287). The reqwest client has auto-redirects disabled and a 120s timeout, so this is the last hop answering with a hard failure - typically 403 from an expired signed storage URL, 404 after the artifact was purged, 429 rate limiting, or a 5xx from the storage backend. The concrete HTTP status is embedded in the message.","triggerScenarios":"Generation succeeds but the download GET returns >= 400: the fal.ai signed storage link expired before the GET landed (slow NAT64 resolution, slow disk write, or queued download), the object was already deleted, the CDN rate-limited the egress IP, or the origin rejected the NAT64-mapped source address with 403.","commonSituations":"Large images where time passes between generation and download; shared egress IPs hitting CDN 429 limits; misconfigured security.nat64_prefixes routing downloads through addresses the CDN blocks; brief fal.ai storage incidents returning 5xx.","solutions":["Read the HTTP status in the message and branch: 403/404 means the URL is dead, regenerate; 429 means back off and retry; 5xx means retry shortly","Retry the whole generate call to obtain a fresh signed URL","Tighten the gap between generation and download - avoid slow synchronous work in between","If 403 persists, review the security.nat64_prefixes configuration so downloads originate from an address the CDN accepts"],"exampleFix":"// before: single shot\nlet out = image_gen.execute(args).await?;\n\n// after: retry once on download HTTP failure (fresh signed URL)\nlet out = match image_gen.execute(args.clone()).await {\n    Ok(out) => out,\n    Err(e) if e.to_string()\n        .starts_with(\"Generated image download failed with HTTP\") =>\n        image_gen.execute(args).await?,\n    Err(e) => return Err(e),\n};","handlingStrategy":"retry","validationCode":null,"typeGuard":"fn is_image_download_http_failure(err: &anyhow::Error) -> bool {\n    err.to_string()\n        .starts_with(\"Generated image download failed with HTTP\")\n}","tryCatchPattern":"match image_gen.execute(args).await {\n    Ok(result) => result,\n    Err(e) if is_image_download_http_failure(&e) => {\n        let msg = e.to_string();\n        if msg.contains(\"HTTP 429\") || msg.contains(\"HTTP 5\") {\n            backoff_then_retry_once(e) // transient: rate limit or server error\n        } else {\n            regenerate_image(e) // 403/404: signed URL is dead\n        }\n    }\n    Err(e) => return Err(e),\n}","preventionTips":["Regenerate instead of reusing old image URLs - signed links are short-lived","Rate-limit concurrent image downloads from a single egress IP","Keep security.nat64_prefixes aligned with source addresses the CDN accepts","Treat 429/5xx as retryable and 403/404 as regenerate"],"tags":["rust","zeroclaw","fal-ai","http-status","signed-url","image-download"],"backgroundTag":"http-error-status","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}