HKUDS/Vibe-Trading · error · RuntimeError

getuploadurl returned no upload URL (need upload_full_url or

Error message

getuploadurl returned no upload URL (need upload_full_url or upload_param): {upload_resp}

What it means

During media upload, the channel calls ilink/bot/getuploadurl and needs either upload_full_url or upload_param to know where to PUT the encrypted bytes. If both are absent/empty in the response, it raises RuntimeError embedding the full response, since the upload cannot proceed without a target URL.

Source

Thrown at agent/src/channels/weixin.py:1394

        file_key = os.urandom(16).hex()
        upload_body: dict[str, Any] = {
            "filekey": file_key,
            "media_type": upload_type,
            "to_user_id": to_user_id,
            "rawsize": raw_size,
            "rawfilemd5": raw_md5,
            "filesize": padded_size,
            "no_need_thumb": True,
            "aeskey": aes_key_hex,
        }

        assert self._client is not None
        upload_resp = await self._api_post("ilink/bot/getuploadurl", upload_body)

        upload_full_url = str(upload_resp.get("upload_full_url", "") or "").strip()
        upload_param = str(upload_resp.get("upload_param", "") or "")
        if not upload_full_url and not upload_param:
            raise RuntimeError(
                "getuploadurl returned no upload URL "
                f"(need upload_full_url or upload_param): {upload_resp}"
            )

        # Step 2: AES-128-ECB encrypt and POST to CDN
        aes_key_b64 = base64.b64encode(aes_key_raw).decode()
        encrypted_data = _encrypt_aes_ecb(raw_data, aes_key_b64)

        if upload_full_url:
            cdn_upload_url = upload_full_url
        else:
            cdn_upload_url = (
                f"{self.config.cdn_base_url}/upload"
                f"?encrypted_query_param={quote(upload_param)}"
                f"&filekey={quote(file_key)}"
            )

        cdn_resp = await self._client.post(

View on GitHub (pinned to 80ffdda44c)

Solutions

  1. Inspect upload_resp in the message: an errcode/ret present indicates auth or quota issues to fix first.
  2. Retry after a delay if quota-related; media upload slots are often rate-limited.
  3. Re-authenticate if the token is stale (session pause can degrade subsequent calls).
  4. Verify the upload_body media type/size fields match supported CDN media kinds.

Example fix

// before
upload_resp = await self._api_post("ilink/bot/getuploadurl", upload_body)

// after
upload_resp = await self._api_post("ilink/bot/getuploadurl", upload_body)
if not (upload_resp.get("upload_full_url") or upload_resp.get("upload_param")):
    self.logger.warning("getuploadurl refused: {}", upload_resp)
    await asyncio.sleep(30)  # retry / fall back to text message
Defensive patterns

Strategy: retry

Try / catch

try:
    await channel.send_media(chat_id, path)
except RuntimeError as e:
    if "no upload URL" in str(e):
        await asyncio.sleep(30)
        await channel.send_media(chat_id, path)
    else:
        raise

Prevention

When it happens

Trigger: _send_media_file gets a getuploadurl response where upload_full_url is empty/whitespace and upload_param is empty — server refused to allocate an upload slot (auth, quota, or unsupported media type).

Common situations: WeChat CDN upload quota exhausted for the bot account; session/token degraded so the endpoint returns an error object instead of upload fields; media type or file metadata in upload_body not accepted by the server; API schema change renaming the fields.

Related errors


AI-assisted analysis of HKUDS/Vibe-Trading@80ffdda44c (2026-08-28). Data as JSON: /api/errors/37981859037c31dd. Report an issue: GitHub.