{"record":{"id":"b70bc0a78e0b59a1","repo":"zeroclaw-labs/zeroclaw","slug":"generated-image-url-must-be-a-non-empty-url-withou","errorCode":null,"errorMessage":"Generated image URL must be a non-empty URL without whitespace","messagePattern":"Generated image URL must be a non-empty URL without whitespace","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/zeroclaw-tools/src/image_gen.rs","lineNumber":27,"sourceCode":"use zeroclaw_api::tool::{Tool, ToolOutput, ToolResult, with_ephemeral_workspace_warning};\nuse zeroclaw_config::policy::SecurityPolicy;\nuse zeroclaw_config::policy::ToolOperation;\n\nconst FAL_RESPONSE_LIMIT_BYTES: usize = 1024 * 1024;\nconst FAL_ERROR_LIMIT_BYTES: usize = 16 * 1024;\nconst GENERATED_IMAGE_LIMIT_BYTES: usize = 20 * 1024 * 1024;\nconst MAX_IMAGE_REDIRECTS: usize = 10;\n\nstruct ValidatedImageTarget {\n    url: reqwest::Url,\n    host: String,\n    resolved_addrs: Vec<SocketAddr>,\n}\n\nfn parse_public_https_url(raw_url: &str) -> anyhow::Result<(reqwest::Url, String, u16)> {\n    let raw_url = raw_url.trim();\n    if raw_url.is_empty() || raw_url.chars().any(char::is_whitespace) {\n        anyhow::bail!(\"Generated image URL must be a non-empty URL without whitespace\");\n    }\n\n    let mut url = reqwest::Url::parse(raw_url).context(\"Invalid generated image URL\")?;\n    if url.scheme() != \"https\" {\n        anyhow::bail!(\"Generated image URL must use HTTPS\");\n    }\n    if !url.username().is_empty() || url.password().is_some() {\n        anyhow::bail!(\"Generated image URL userinfo is not allowed\");\n    }\n\n    let request_host = url\n        .host_str()\n        .ok_or_else(|| anyhow::Error::msg(\"Generated image URL must include a host\"))?;\n    if request_host.ends_with('.') {\n        anyhow::bail!(\"Generated image URL host must not end with a dot\");\n    }\n    let host = domain_guard::normalize_domain(request_host)\n        .ok_or_else(|| anyhow::Error::msg(\"Generated image URL host is invalid\"))?;","sourceCodeStart":9,"sourceCodeEnd":45,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-tools/src/image_gen.rs#L9-L45","documentation":"parse_public_https_url is the first gate on the image URL that the fal.ai image-generation tool extracts from the provider response. After trimming, the URL must be non-empty and contain no whitespace characters; anything else is rejected before parsing. In practice this error means the field pulled out of the fal.ai JSON was empty, truncated, or contained embedded spaces/newlines, so the downstream image download never starts.","triggerScenarios":"The fal.ai request succeeds but the extracted URL field is empty (wrong field name for the model, e.g. reading 'url' when the model returns 'image_url' or an output array), a truncated or wrapped URL containing a newline, or a response shape change after a model/app version bump.","commonSituations":"Switching fal.ai models or app revisions whose payload uses a different JSON shape, extracting the wrong key from the response JSON, an auth failure body being parsed as if it were a success payload, and copy-pasted test URLs containing stray whitespace.","solutions":["Log or inspect the raw (bounded) fal.ai response body and check which field actually carries the image URL for the model you selected.","Update the extraction to the correct field (e.g. images[0].url vs output[0] vs image_url) for the current model version.","Trim the URL and verify it is non-empty before passing it on; treat an empty URL from the provider as a provider-side failure and retry the generation."],"exampleFix":"// before\nlet url = payload[\"url\"].as_str().unwrap_or(\"\");\n\n// after\nlet url = payload[\"images\"][0][\"url\"].as_str().map(str::trim).filter(|u| !u.is_empty() && !u.chars().any(char::is_whitespace)).ok_or_else(|| anyhow::anyhow!(\"model returned no image URL\"))?;","handlingStrategy":"validation","validationCode":"fn clean_image_url(raw: &str) -> Option<&str> {\n    let t = raw.trim();\n    (!t.is_empty() && !t.chars().any(char::is_whitespace)).then_some(t)\n}","typeGuard":"fn is_non_empty_url(s: &str) -> bool {\n    let t = s.trim();\n    !t.is_empty() && !t.chars().any(char::is_whitespace)\n}","tryCatchPattern":"match validate_image_target(&url, nat64).await {\n    Err(e) if e.to_string().contains(\"must be a non-empty URL\") => {\n        // provider returned a broken field: log the raw fal payload and retry generation once\n    }\n    other => other,\n}","preventionTips":["Pin fal.ai model/app revisions so response JSON shape changes do not silently break field extraction.","Trim and sanity-check provider-supplied URLs before use; treat empty as a provider failure and retry.","Log the (bounded) provider response on failure so wrong-field extraction is obvious."],"tags":["url","validation","image-gen","fal-ai","rust"],"backgroundTag":"empty-or-malformed-url","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}