{"record":{"id":"bacfe289c1ec4102","repo":"JuliusBrussee/caveman","slug":"name-must-not-contain-surrounding-whitespace-core","errorCode":null,"errorMessage":"{name} must not contain surrounding whitespace","messagePattern":"(.+?) must not contain surrounding whitespace","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"packages/sdk/python/caveman_cloud/core.py","lineNumber":331,"sourceCode":"# Runtime-policy constants (see the RuntimePolicyClient section at the bottom of\n# this module). Declared here because Cave.runtime_policy defaults to the env name.\n_POLICY_KILL_ENV = \"CAVEMAN_POLICY_KILL\"\n_POLICY_PATH = \"/sdk/v1/runtime-policy\"\n_POLICY_SCHEMA_VERSION = \"caveman.runtime-policy.v1\"\n_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","sourceCodeStart":313,"sourceCodeEnd":349,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/3ee70a102609e550bd2e68004bf5990a9341c851/packages/sdk/python/caveman_cloud/core.py#L313-L349","documentation":"`_normalized_service_url` validates a service URL field (e.g. a `service_url` dataclass attribute) when the config object is instantiated via `__post_init__`. The library requires callers to pass pre-trimmed URLs; leading/trailing whitespace would otherwise silently change routing or auth signing. It throws this ValueError when `value.strip() != value`.","triggerScenarios":"Constructing a config/dataclass (e.g. `Cave` or a service settings object) whose URL attribute is read from an env var, YAML/JSON config, or CLI input that contains a leading or trailing space, tab, or newline (common with `readline()` on files).","commonSituations":"Reading `CAVE_SERVICE_URL` from an environment variable with a stray space, appending a newline from a file read without `.strip()`, or copy-pasting a URL with a trailing blank.","solutions":["Call `.strip()` on the URL before constructing the object.","Fix the source of the whitespace (trim when reading env vars or files).","Set the URL directly as a literal with no surrounding whitespace."],"exampleFix":"// before\nservice_url = os.environ[\"CAVE_SERVICE_URL\"]\ncave = Cave(api_key=key, service_url=service_url)\n// after\nservice_url = os.environ[\"CAVE_SERVICE_URL\"].strip()\ncave = Cave(api_key=key, service_url=service_url)","handlingStrategy":"validation","validationCode":"def valid_service_url(v: str) -> bool:\n    return isinstance(v, str) and v.strip() == v and bool(v)","typeGuard":null,"tryCatchPattern":"try:\n    cave = Cave(api_key=key, service_url=url)\nexcept ValueError as e:\n    logger.error(\"invalid service url: %s\", e)\n    raise ConfigError(str(e)) from e","preventionTips":["Always .strip() URLs read from env vars or files","Add the trim in your config-loading layer, not at call sites","Fail fast at startup with a clear message if the URL is malformed"],"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"}