{"record":{"id":"d3ec5e9ca9bc2f14","repo":"Comfy-Org/ComfyUI","slug":"invalid-hash","errorCode":"INVALID_HASH","errorMessage":"hash must be like 'blake3:<hex>'","messagePattern":"hash must be like 'blake3:<hex>'","errorType":"http","errorClass":"UploadError","httpStatus":400,"severity":"error","filePath":"app/assets/api/upload.py","lineNumber":21,"sourceCode":"import uuid\nfrom typing import Callable\n\nfrom aiohttp import web\n\nimport folder_paths\nfrom app.assets.api.schemas_in import ParsedUpload, UploadError\nfrom app.assets.helpers import validate_blake3_hash\n\n\ndef normalize_and_validate_hash(s: str) -> str:\n    \"\"\"Validate and normalize a hash string.\n\n    Returns canonical 'blake3:<hex>' or raises UploadError.\n    \"\"\"\n    try:\n        return validate_blake3_hash(s)\n    except ValueError:\n        raise UploadError(400, \"INVALID_HASH\", \"hash must be like 'blake3:<hex>'\")\n\n\nasync def parse_multipart_upload(\n    request: web.Request,\n    check_hash_exists: Callable[[str], bool],\n) -> ParsedUpload:\n    \"\"\"\n    Parse a multipart/form-data upload request.\n\n    Args:\n        request: The aiohttp request\n        check_hash_exists: Callable(hash_str) -> bool to check if a hash exists\n\n    Returns:\n        ParsedUpload with parsed fields and temp file path\n\n    Raises:\n        UploadError: On validation or I/O errors","sourceCodeStart":3,"sourceCodeEnd":39,"githubUrl":"https://github.com/Comfy-Org/ComfyUI/blob/1c6d8d45b3693bfbb32385b410d813a7fd6be216/app/assets/api/upload.py#L3-L39","documentation":"Raised from the HTTP >=400 branch (comfy_api_nodes/util/client.py:823) via _friendly_http_message for status 429: 'Rate Limit Exceeded: The server returned 429 after all retry attempts. Please wait and try again.' The client already honors Retry-After and re-attempts rate-limited requests (max_retries_on_rate_limit); this error means even those retries kept hitting 429.","triggerScenarios":"Firing many API nodes in rapid succession (parallel queued workflows); a single account shared by multiple machines; retry storms where backoff windows are shorter than the provider's limit window; provider-side throttling during peak load.","commonSituations":"Batch runs with high fan-out; automation scripts without pacing; multiple users on one API key; provider rate-limit tier too low for the workload.","solutions":["Wait for the limit window to reset, then retry with fewer concurrent API nodes","Reduce workflow parallelism (fewer simultaneous API-account nodes) or space submissions with a delay","Increase max_retries_on_rate_limit / retry_backoff so the client's built-in backoff covers the provider's window","Check the response headers in the request log for Retry-After and pace accordingly"],"exampleFix":"// before\nawait sync_op_raw(cls, endpoint, max_retries_on_rate_limit=2, retry_backoff=1.5)\n\n// after\nawait sync_op_raw(cls, endpoint, max_retries_on_rate_limit=6, retry_backoff=3.0)","handlingStrategy":"retry","validationCode":"# pace submissions below the provider's limit\nasync def bounded(nodes, coros, limit=2, gap=1.0):\n    sem = asyncio.Semaphore(limit)\n    async def run(c):\n        async with sem:\n            await asyncio.sleep(gap)\n            return await c\n    return await asyncio.gather(*(run(c) for c in coros))","typeGuard":null,"tryCatchPattern":"try:\n    result = await sync_op(...)\nexcept Exception as e:\n    if \"Rate Limit Exceeded\" in str(e):\n        await asyncio.sleep(retry_after_from_log or 60)\n        result = await sync_op(...)\n    else:\n        raise","preventionTips":["Throttle concurrent API node executions in batch workflows","Configure max_retries_on_rate_limit/retry_backoff generously enough to cover the provider's window"],"tags":["rate-limit","http-429","retry","throughput"],"backgroundTag":null,"analyzedSha":"1c6d8d45b3693bfbb32385b410d813a7fd6be216","analyzedAt":"2026-08-14T19:37:18.893Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}