JuliusBrussee/caveman · error · ValueError
must not contain a query or fragment
Error message
{name} must not contain a query or fragment What it means
`_normalized_service_url` rejects URLs that carry a query string or fragment. Service base URLs must be a bare origin (+path); query parameters and `#fragments` have no meaning for the API base and would corrupt request routing.
Solutions
- Strip everything from `?` and `#` onward, keeping only scheme://host[/path].
- Set the base URL, e.g. `https://cave.example.com` instead of the full page URL.
- Normalize the value in config-loading code before constructing the object.
Example fix
// before service_url = "https://cave.example.com/?utm_source=docs#/overview" // after service_url = "https://cave.example.com"
Defensive patterns
Strategy: validation
Validate before calling
from urllib.parse import urlsplit
def url_has_query_or_fragment(v: str) -> bool:
p = urlsplit(v)
return bool(p.query or p.fragment)
# strip with v.split('?')[0].split('#')[0] if True Try / catch
try:
cave = Cave(api_key=key, service_url=url)
except ValueError:
url = url.split('?')[0].split('#')[0].rstrip('/')
cave = Cave(api_key=key, service_url=url) Prevention
- Copy only the origin/base URL, not the browser address bar value
- Normalize URLs (strip ?/#) in config loading
- Keep page URLs and API base URLs in separate config keys
When it happens
Trigger: Passing `https://cave.example.com?env=prod`, `https://cave.example.com/#/dashboard`, or a full page URL (copied from a browser address bar) as the service URL.
Common situations: Copy-pasting the URL straight from a browser (fragments/UTM query strings included) into config or an env var.
Understand the failure class
Background: "Invalid URL" errors: why new URL(), URI.parse, and reqwest::Url reject your string — missing scheme, whitespace, and bad path format — this error's family across 39 libraries.
Related errors
- invalid_endpoint
- must be an absolute http(s) URL
- must be an absolute http(s) URL without credentials
- must not contain surrounding whitespace
- artifact_id is required
AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20).
Data as JSON: /api/errors/56b89251e424e3e7.
Report an issue: GitHub.
Appendix: source
Thrown at packages/sdk/python/caveman_cloud/core.py:345
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 = False
# control_url is reserved for control-plane APIs and defaults to base_url.
control_url: str | None = NoneView on GitHub (pinned to 3ee70a1026)