{"record":{"id":"db69bddef7f79756","repo":"unslothai/unsloth","slug":"virustotal-did-not-return-an-upload-url","errorCode":null,"errorMessage":"VirusTotal did not return an upload URL","messagePattern":"VirusTotal did not return an upload URL","errorType":"exception","errorClass":"RuntimeError","httpStatus":null,"severity":"warning","filePath":"scripts/virustotal_scan.py","lineNumber":497,"sourceCode":"\n        Every desktop bundle is 41-46 MB, which is over the 32 MB cap on\n        `POST /files`, so the signed upload URL is the only path that works here.\n\n        Each signed URL is SINGLE USE, so the POST is issued with retries disabled.\n        Replaying one after, say, the response body failed to read would be rejected\n        no matter how many times we tried, and would report the asset as unavailable\n        while an analysis was in fact already running. Retrying instead means going\n        back for a fresh URL, which is what the loop below does.\n        \"\"\"\n        attempts = max(1, _UPLOAD_ATTEMPTS)\n        last_error: Exception | None = None\n        body, content_type = _build_multipart(path)\n\n        for attempt in range(1, attempts + 1):\n            _, payload = self.request(\"GET\", f\"{API_ROOT}/files/upload_url\", deadline = deadline)\n            upload_url = payload.get(\"data\") if isinstance(payload, dict) else None\n            if not isinstance(upload_url, str) or not upload_url:\n                raise RuntimeError(\"VirusTotal did not return an upload URL\")\n            # Mask before the URL is ever used, so anything that later echoes it -- a\n            # traceback, a future debug print, a library error string -- is scrubbed.\n            _mask_in_actions(upload_url)\n\n            try:\n                _, payload = self.request(\n                    \"POST\",\n                    upload_url,\n                    body = body,\n                    extra_headers = {\"content-type\": content_type},\n                    max_attempts = 1,\n                    deadline = deadline,\n                )\n            except TimeoutError:\n                raise\n            except Exception as error:\n                last_error = error\n                if attempt < attempts:","sourceCodeStart":479,"sourceCodeEnd":515,"githubUrl":"https://github.com/unslothai/unsloth/blob/203007d19051dcd2ae33876786d117c99f6b0368/scripts/virustotal_scan.py#L479-L515","documentation":"Thrown by renameChatProject (use-chat-projects.ts:126-133) when the new project name is empty after trimming — the guard runs client-side before updateStoredChatProject persists the rename. It exists because the storage layer would otherwise accept (or silently mishandle) a blank name, leaving an unnamed project row in the sidebar.","triggerScenarios":"Calling renameChatProject(projectId, name) with name containing only whitespace ('', '   ', '\\t'). Any non-whitespace content passes; there is no length or character restriction at this layer.","commonSituations":"Submitting the rename form with an empty input (Enter on a cleared field); programmatic callers passing an untrimmed empty string; UI not disabling the confirm button on empty input.","solutions":["Type a non-empty project name (any non-whitespace characters) and save again.","If you want the project gone rather than renamed, use deleteChatProject instead.","As a developer, disable the rename confirm button while the trimmed input is empty so the throw is never reached."],"exampleFix":"// before\n<DialogConfirm onConfirm={() => renameChatProject(id, draftName)} />;\n\n// after\n<DialogConfirm disabled={!draftName.trim()} onConfirm={() => renameChatProject(id, draftName)} />","handlingStrategy":"validation","validationCode":"function validProjectName(name: string): boolean {\n  return name.trim().length > 0;\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Trim input and disable Confirm while the trimmed value is empty.","Treat a rename-to-empty as an intent to possibly delete — ask the user which they meant.","Apply the same guard in createChatProject callers (createStoredChatProject) for consistency."],"tags":["validation","projects","frontend","form"],"backgroundTag":null,"analyzedSha":"203007d19051dcd2ae33876786d117c99f6b0368","analyzedAt":"2026-08-15T02:48:39.846Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}