{"record":{"id":"6f00fc34e6a500ff","repo":"JuliusBrussee/caveman","slug":"name-must-be-an-absolute-http-s-url-core","errorCode":null,"errorMessage":"{name} must be an absolute http(s) URL","messagePattern":"(.+?) must be an absolute http\\(s\\) URL","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"packages/sdk/python/caveman_cloud/core.py","lineNumber":336,"sourceCode":"_POLICY_MAX_RESPONSE_BYTES = 1024 * 1024\n\n\ndef _env_workflow() -> str:\n    \"\"\"CAVE_WORKFLOW normalized to the gateway label rule, else the honest default.\"\"\"\n    raw = (os.environ.get(\"CAVE_WORKFLOW\") or \"\").lower()\n    if raw and len(raw) <= 96 and all(c in _WORKFLOW_CHARS for c in raw):\n        return raw\n    return \"unlabeled-workflow\"\n\n\ndef _normalized_service_url(value: str, name: str) -> str:\n    if value.strip() != value:\n        raise ValueError(f\"{name} must not contain surrounding whitespace\")\n    try:\n        parsed = urlsplit(value)\n        port = parsed.port\n    except (TypeError, ValueError) as error:\n        raise ValueError(f\"{name} must be an absolute http(s) URL\") from error\n    if (\n        parsed.scheme not in (\"http\", \"https\")\n        or not parsed.hostname\n        or parsed.username is not None\n        or parsed.password is not None\n    ):\n        raise ValueError(f\"{name} must be an absolute http(s) URL without credentials\")\n    if parsed.query or parsed.fragment:\n        raise ValueError(f\"{name} must not contain a query or fragment\")\n    return value.rstrip(\"/\")\n\n\n@dataclass\nclass Cave:\n    api_key: str\n    base_url: str\n    agent: str\n    # CAVE_WORKFLOW lets a wrapper (`cave wrap --workflow x`) label every request","sourceCodeStart":318,"sourceCodeEnd":354,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/3ee70a102609e550bd2e68004bf5990a9341c851/packages/sdk/python/caveman_cloud/core.py#L318-L354","documentation":"`_normalized_service_url` parses the URL with `urlsplit`; if parsing raises (e.g. an invalid port like `:abc`) or the parsed result is not an http/https absolute URL with a hostname, it raises this ValueError. The library only accepts absolute `http://` or `https://` URLs for service endpoints.","triggerScenarios":"Passing a relative path (`/api`), a bare host (`localhost:8080` without scheme), a non-http scheme (`ftp://`), a malformed port, or an empty string as the service URL when constructing the config object.","commonSituations":"Config default of `\"\"` left unfilled, forgetting the `https://` scheme prefix, or using `ws://`/custom schemes against the SDK.","solutions":["Prefix the URL with `https://` (or `http://` for local dev) if the scheme is missing.","Use a full absolute URL including scheme and host, e.g. `https://cave.example.com`.","Check for a malformed port (`host:port` must be numeric) or remove invalid characters.","Ensure the value is non-empty before constructing."],"exampleFix":"// before\ncave = Cave(api_key=key, service_url=\"localhost:8080\")\n// after\ncave = Cave(api_key=key, service_url=\"http://localhost:8080\")","handlingStrategy":"validation","validationCode":"from urllib.parse import urlsplit\ndef is_absolute_http_url(v: str) -> bool:\n    try:\n        p = urlsplit(v)\n    except ValueError:\n        return False\n    return p.scheme in (\"http\", \"https\") and bool(p.hostname)","typeGuard":null,"tryCatchPattern":"try:\n    cave = Cave(api_key=key, service_url=url)\nexcept ValueError:\n    url = \"https://\" + url.lstrip(\"/\")\n    cave = Cave(api_key=key, service_url=url)","preventionTips":["Store full absolute URLs in config, never bare hosts","Include the scheme explicitly, even for localhost","Validate config once at startup"],"tags":["python","validation","url"],"backgroundTag":"invalid-url-format","analyzedSha":"3ee70a102609e550bd2e68004bf5990a9341c851","analyzedAt":"2026-09-20T15:53:39.229Z","contentChangedAt":"2026-09-20T15:53:39.229Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}