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

  1. Type a non-empty project name (any non-whitespace characters) and save again.
  2. If you want the project gone rather than renamed, use deleteChatProject instead.
  3. 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

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


AI-assisted analysis of unslothai/unsloth@203007d190 (2026-08-15). Data as JSON: /api/errors/db69bddef7f79756. Report an issue: GitHub.