{"record":{"id":"10ff847aa15ad85e","repo":"unslothai/unsloth","slug":"provider-base-url-must-contain-a-hostname","errorCode":null,"errorMessage":"Provider base URL must contain a hostname.","messagePattern":"Provider base URL must contain a hostname\\.","errorType":"validation","errorClass":"ValueError","httpStatus":400,"severity":"error","filePath":"studio/backend/core/inference/providers.py","lineNumber":889,"sourceCode":"\n    raw = base_url.strip()\n    if any(char.isspace() or ord(char) < 32 or ord(char) == 127 for char in raw) or \"\\\\\" in raw:\n        raise ValueError(\"Provider base URL contains invalid characters.\")\n\n    try:\n        parts = urlsplit(raw)\n        port = parts.port\n        hostname = parts.hostname\n    except ValueError as exc:\n        raise ValueError(\"Provider base URL is malformed.\") from exc\n\n    scheme = parts.scheme.lower()\n    if scheme not in (\"http\", \"https\"):\n        raise ValueError(\"Provider base URL must use http or https.\")\n    # Userinfo stays allowed for gateways behind basic auth; the checks below read\n    # the parsed hostname, so http://api.openai.com@169.254.169.254/ is caught.\n    if not hostname:\n        raise ValueError(\"Provider base URL must contain a hostname.\")\n\n    hostname = hostname.rstrip(\".\")\n    if _metadata_host(hostname) or _resolves_to_metadata(hostname, port, scheme):\n        raise ValueError(\"Cloud metadata endpoints cannot be used as a provider base URL.\")\n\n    if os.environ.get(_BLOCK_PRIVATE_ENV) == \"1\":\n        _reject_non_public(hostname, port, scheme)\n\n    return raw.rstrip(\"/\")\n\n\ndef list_available_providers(include_hidden: bool = False) -> list[dict[str, Any]]:\n    \"\"\"Return registered providers (for the /registry endpoint).\n\n    Hidden entries exist only for backend lookups and are surfaced by the UI via\n    ``CUSTOM_PROVIDER_PRESETS`` instead of the dropdown, so they stay filtered\n    out by default. That default is load-bearing for upgrades: a browser holding\n    a cached bundle from before this capability existed has no idea to filter on","sourceCodeStart":871,"sourceCodeEnd":907,"githubUrl":"https://github.com/unslothai/unsloth/blob/203007d19051dcd2ae33876786d117c99f6b0368/studio/backend/core/inference/providers.py#L871-L907","documentation":"The URL parsed and has an http(s) scheme, but parts.hostname is empty — e.g. 'https:///v1' or 'https://:8080/v1'. Note the check runs after userinfo handling, so tricks like http://api.openai.com@169.254.169.254/ still yield the metadata IP as hostname and are caught by the later metadata check; this error is only for a genuinely absent host.","triggerScenarios":"Base URLs with empty host: 'https:///v1', 'http://:9000/', or template-generated URLs where the host placeholder was never substituted.","commonSituations":"Config templating leaving {HOST} empty; users deleting the host while editing; constructing URLs by string concatenation with an empty host variable.","solutions":["Include the hostname: 'https://api.openai.com/v1'.","Check templating/config interpolation for empty host variables.","Validate presence of a host in client-side forms before submit."],"exampleFix":"# before\nvalidate_provider_base_url(f\"https://{host}/v1\")  # host == \"\"\n# after\nif not host:\n    raise ValueError(\"host missing\")\nvalidate_provider_base_url(f\"https://{host}/v1\")","handlingStrategy":"validation","validationCode":"from urllib.parse import urlsplit\nassert urlsplit(raw).hostname, \"URL must include a hostname\"","typeGuard":"def has_hostname(raw: str) -> bool:\n    from urllib.parse import urlsplit\n    return bool(urlsplit(raw).hostname)","tryCatchPattern":null,"preventionTips":["Check templated URLs for unfilled host placeholders before submission.","Build URLs from (scheme, host, path) parts rather than string concatenation."],"tags":["validation","url-parsing","provider-config"],"backgroundTag":null,"analyzedSha":"203007d19051dcd2ae33876786d117c99f6b0368","analyzedAt":"2026-08-15T02:48:39.846Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}