JuliusBrussee/caveman · error · ValueError
{name} must be an absolute http(s) URL without credentials
Error message
{name} must be an absolute http(s) URL without credentials What it means
ValueError from _normalized_service_url in packages/sdk/python/caveman_cloud/core.py, raised when urlsplit succeeds but the URL is still not acceptable: the scheme is not http/https (ftp:, file:, empty), there is no hostname (scheme-only strings like "https://"), or the URL embeds credentials (user:pass@host). The SDK wants credentials supplied via the api_key field, never inline in the URL, where they would leak into logs and error messages.
Source
Thrown at packages/sdk/python/caveman_cloud/core.py:321
return raw
return "unlabeled-workflow"
def _normalized_service_url(value: str, name: str) -> str:
if value.strip() != value:
raise ValueError(f"{name} must not contain surrounding whitespace")
try:
parsed = urlsplit(value)
port = parsed.port
except (TypeError, ValueError) as error:
raise ValueError(f"{name} must be an absolute http(s) URL") from error
if (
parsed.scheme not in ("http", "https")
or not parsed.hostname
or parsed.username is not None
or parsed.password is not None
):
raise ValueError(f"{name} must be an absolute http(s) URL without credentials")
if parsed.query or parsed.fragment:
raise ValueError(f"{name} must not contain a query or fragment")
return value.rstrip("/")
@dataclass
class Cave:
api_key: str
base_url: str
agent: str
# CAVE_WORKFLOW lets a wrapper (`cave wrap --workflow x`) label every request
# from an SDK app without a code change. An explicit value always wins. The
# env value is normalized to the gateway's label rule (lowercase [a-z0-9_-],
# max 96); an invalid ambient value is ignored rather than 400-ing every
# request. Mirrors @caveman-ai/sdk (TypeScript).
default_workflow: str = field(default_factory=lambda: _env_workflow())
retention: str = "metadata"
verify_on_init: bool = FalseView on GitHub (pinned to 27d5a3981a)
Solutions
- Normalize to an absolute URL with scheme and host, e.g. "https://gateway.example.com", no trailing slash needed (the function strips it).
- Move any user:pass@ credentials out of the URL and into the api_key parameter.
- Add the https:// prefix when reading bare hostnames from configuration.
Example fix
# before Cave(api_key=..., base_url="https://user:secret@gateway.example.com", agent=...) # after Cave(api_key="secret", base_url="https://gateway.example.com", agent=...)
Defensive patterns
Strategy: validation
Validate before calling
from urllib.parse import urlsplit
def normalize_base_url(value: str) -> str:
p = urlsplit(value)
if p.scheme not in ("http", "https") or not p.hostname or p.username is not None:
raise ValueError(f"base_url must be an absolute http(s) URL without credentials: {value!r}")
return value.rstrip("/") Prevention
- Always include the https:// scheme in configured hostnames.
- Pass credentials through Cave(api_key=...), never inline in the URL.
- Centralize URL normalization in one config-loading helper so every entrypoint gets the same check.
When it happens
Trigger: Passing "gateway.example.com" (no scheme, so parsed.scheme is empty), "ftp://host", "https://" with no host, or "https://user:secret@gateway.example.com" to Cave construction for base_url or any other service URL.
Common situations: Forgetting the https:// prefix on a hostname from config; environments where the base URL was stored without scheme; attempting to pass an api key in the URL out of habit from other clients; scheme values with uppercase or trailing colon typos.
Related errors
- {name} must not contain surrounding whitespace
- {name} must be an absolute http(s) URL
- cave_sandbox_credential_capability_ambiguous
- artifactId is required
- retry loop interrupted: tool call {signature!r} repeated {re
AI-assisted analysis of JuliusBrussee/caveman@27d5a3981a (2026-08-15).
Data as JSON: /api/errors/4143e9fa36444da9.
Report an issue: GitHub.