{"record":{"id":"d6fdd03e4de6a66b","repo":"NousResearch/hermes-agent","slug":"image-at-url-returned-0-bytes-refusing-to-cache","errorCode":null,"errorMessage":"Image at {url} returned 0 bytes; refusing to cache.","messagePattern":"Image at (.+?) returned 0 bytes; refusing to cache\\.","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"agent/image_gen_provider.py","lineNumber":342,"sourceCode":"                continue\n            bytes_written += len(chunk)\n            if bytes_written > max_bytes:\n                fh.close()\n                try:\n                    path.unlink()\n                except OSError:\n                    pass\n                raise ValueError(\n                    f\"Image at {url} exceeds {max_bytes // (1024 * 1024)}MB cap; refusing to cache.\"\n                )\n            fh.write(chunk)\n\n    if bytes_written == 0:\n        try:\n            path.unlink()\n        except OSError:\n            pass\n        raise ValueError(f\"Image at {url} returned 0 bytes; refusing to cache.\")\n\n    return path\n\n\ndef success_response(\n    *,\n    image: str,\n    model: str,\n    prompt: str,\n    aspect_ratio: str,\n    provider: str,\n    modality: str = \"text\",\n    extra: Optional[Dict[str, Any]] = None,\n) -> Dict[str, Any]:\n    \"\"\"Build a uniform success response dict.\n\n    ``image`` may be an HTTP URL or an absolute filesystem path (for b64\n    providers like OpenAI). ``modality`` is ``\"text\"`` (text-to-image) or","sourceCodeStart":324,"sourceCodeEnd":360,"githubUrl":"https://github.com/NousResearch/hermes-agent/blob/c896c09c42910c584c4c7d2325b58c14713ea42c/agent/image_gen_provider.py#L324-L360","documentation":"Raised by the image-generation provider's cache writer after streaming an image from a remote URL produced zero bytes. The empty file is unlinked and the download is aborted so a 0-byte artifact never poisons the cache. It signals that the provider URL responded but delivered no body (or the read loop received no chunks).","triggerScenarios":"Calling the image cache/download path (e.g. image_gen tool with a provider that returns a URL) where the HTTP response body is empty: a signed URL that expired and returns 200 with no content, a redirect handled without follow, or a provider that streams zero chunks before closing.","commonSituations":"Expired or mis-scoped presigned S3/GCS URLs; provider API changes returning metadata instead of bytes; proxies or gateways stripping the body; transient provider outages that return 200-empty.","solutions":["Retry the generation once — transient empty-body responses from image providers usually succeed on a second call.","Verify the URL is fresh: if the provider hands back short-lived signed URLs, re-request the image metadata to obtain a new URL before downloading.","Check with curl -I <url> that the URL actually returns Content-Length > 0 and a 200; if it 403s or redirects, fix the provider credentials/redirect handling.","Inspect provider API docs/changelog for response-format changes (e.g. b64_json vs url) and update the response parsing."],"exampleFix":"// before\nconst result = await provider.generate({ prompt });\nawait cacheImage(result.url); // throws ValueError: 0 bytes\n\n// after\nconst result = await provider.generate({ prompt });\nconst head = await fetch(result.url, { method: 'HEAD' });\nif (!head.ok || Number(head.headers.get('content-length') ?? 0) === 0) {\n  throw new Error(`provider returned empty image at ${result.url}; retry generation`);\n}\nawait cacheImage(result.url);","handlingStrategy":"retry","validationCode":"import urllib.request\n\ndef image_url_has_content(url: str) -> bool:\n    req = urllib.request.Request(url, method=\"HEAD\")\n    with urllib.request.urlopen(req, timeout=10) as resp:\n        return resp.status == 200 and int(resp.headers.get(\"Content-Length\") or 0) > 0","typeGuard":null,"tryCatchPattern":"try:\n    path = cache_image(url)\nexcept ValueError as e:\n    if \"0 bytes\" in str(e):\n        path = cache_image(refresh_image_url(url))  # one retry with a fresh URL\n    else:\n        raise","preventionTips":["Re-check signed URLs for freshness before downloading; short-lived URLs are the top cause of empty bodies.","HEAD-check Content-Length > 0 before handing the URL to the cache writer.","Log the provider response status and headers when a download fails so empty 200s are distinguishable from auth failures."],"tags":["image-generation","network","cache","validation"],"backgroundTag":null,"analyzedSha":"c896c09c42910c584c4c7d2325b58c14713ea42c","analyzedAt":"2026-08-14T17:18:01.089Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}