unslothai/unsloth · warning · RuntimeError
VirusTotal did not return an upload URL
Error message
VirusTotal did not return an upload URL
What it means
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.
Source
Thrown at scripts/virustotal_scan.py:497
Every desktop bundle is 41-46 MB, which is over the 32 MB cap on
`POST /files`, so the signed upload URL is the only path that works here.
Each signed URL is SINGLE USE, so the POST is issued with retries disabled.
Replaying one after, say, the response body failed to read would be rejected
no matter how many times we tried, and would report the asset as unavailable
while an analysis was in fact already running. Retrying instead means going
back for a fresh URL, which is what the loop below does.
"""
attempts = max(1, _UPLOAD_ATTEMPTS)
last_error: Exception | None = None
body, content_type = _build_multipart(path)
for attempt in range(1, attempts + 1):
_, payload = self.request("GET", f"{API_ROOT}/files/upload_url", deadline = deadline)
upload_url = payload.get("data") if isinstance(payload, dict) else None
if not isinstance(upload_url, str) or not upload_url:
raise RuntimeError("VirusTotal did not return an upload URL")
# Mask before the URL is ever used, so anything that later echoes it -- a
# traceback, a future debug print, a library error string -- is scrubbed.
_mask_in_actions(upload_url)
try:
_, payload = self.request(
"POST",
upload_url,
body = body,
extra_headers = {"content-type": content_type},
max_attempts = 1,
deadline = deadline,
)
except TimeoutError:
raise
except Exception as error:
last_error = error
if attempt < attempts:View on GitHub (pinned to 203007d190)
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.
Example fix
// before
<DialogConfirm onConfirm={() => renameChatProject(id, draftName)} />;
// after
<DialogConfirm disabled={!draftName.trim()} onConfirm={() => renameChatProject(id, draftName)} /> Defensive patterns
Strategy: validation
Validate before calling
function validProjectName(name: string): boolean {
return name.trim().length > 0;
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- model load did not reach ready
- unknown family '{name}'
- Refused notebook fetch from {host!r}: not in allowlist {sort
- Cell uses shell metacharacters / interpolation but --no-allo
- load error: {backend.load_progress()}
AI-assisted analysis of unslothai/unsloth@203007d190 (2026-08-15).
Data as JSON: /api/errors/db69bddef7f79756.
Report an issue: GitHub.