JuliusBrussee/caveman · error · ValueError

{name} must not contain surrounding whitespace

Error message

{name} must not contain surrounding whitespace

What it means

ValueError from _normalized_service_url in packages/sdk/python/caveman_cloud/core.py when a service URL passed to the Cave client has leading or trailing whitespace (value.strip() != value). This is the first, cheapest sanity check before urlsplit runs; it exists so a stray space from an env var or config file fails loudly at construction time instead of producing a mangled request URL later.

Source

Thrown at packages/sdk/python/caveman_cloud/core.py:309

# Runtime-policy constants (see the RuntimePolicyClient section at the bottom of
# this module). Declared here because Cave.runtime_policy defaults to the env name.
_POLICY_KILL_ENV = "CAVEMAN_POLICY_KILL"
_POLICY_PATH = "/sdk/v1/runtime-policy"
_POLICY_SCHEMA_VERSION = "caveman.runtime-policy.v1"
_POLICY_MAX_RESPONSE_BYTES = 1024 * 1024


def _env_workflow() -> str:
    """CAVE_WORKFLOW normalized to the gateway label rule, else the honest default."""
    raw = (os.environ.get("CAVE_WORKFLOW") or "").lower()
    if raw and len(raw) <= 96 and all(c in _WORKFLOW_CHARS for c in raw):
        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

View on GitHub (pinned to 27d5a3981a)

Solutions

  1. Strip the value before passing it: Cave(..., base_url=os.environ["CAVE_BASE_URL"].strip()).
  2. Fix the source: remove the stray space/newline in the .env file, CI secret, or config entry.
  3. Keep credentials-bearing or padded values out of the URL itself; pass them via the api_key field.

Example fix

# before
base_url=os.environ["CAVE_BASE_URL"]
# after
base_url=os.environ["CAVE_BASE_URL"].strip()
Defensive patterns

Strategy: validation

Validate before calling

def clean_service_url(value: str) -> str:
    value = value.strip()
    if not value:
        raise ValueError("service URL is empty")
    return value

base_url = clean_service_url(os.environ["CAVE_BASE_URL"])

Prevention

When it happens

Trigger: Constructing Cave with base_url or another service URL like " https://gateway.example.com " — commonly a value read from a .env file or shell variable that picked up a trailing newline/space, or a quoted config string with padding.

Common situations: Values loaded from dotenv/CI secrets containing trailing newlines; YAML config parsed with folded scalars adding whitespace; copy-pasting URLs with an accidental leading space.

Related errors


AI-assisted analysis of JuliusBrussee/caveman@27d5a3981a (2026-08-15). Data as JSON: /api/errors/bacfe289c1ec4102. Report an issue: GitHub.