JuliusBrussee/caveman · error · ValueError

must not contain surrounding whitespace

Error message

{name} must not contain surrounding whitespace

What it means

`_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`.

Solutions

  1. Call `.strip()` on the URL before constructing the object.
  2. Fix the source of the whitespace (trim when reading env vars or files).
  3. Set the URL directly as a literal with no surrounding whitespace.

Example fix

// before
service_url = os.environ["CAVE_SERVICE_URL"]
cave = Cave(api_key=key, service_url=service_url)
// after
service_url = os.environ["CAVE_SERVICE_URL"].strip()
cave = Cave(api_key=key, service_url=service_url)
Defensive patterns

Strategy: validation

Validate before calling

def valid_service_url(v: str) -> bool:
    return isinstance(v, str) and v.strip() == v and bool(v)

Try / catch

try:
    cave = Cave(api_key=key, service_url=url)
except ValueError as e:
    logger.error("invalid service url: %s", e)
    raise ConfigError(str(e)) from e

Prevention

When it happens

Trigger: 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).

Common situations: 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.

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


AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20). Data as JSON: /api/errors/bacfe289c1ec4102. Report an issue: GitHub.

Appendix: source

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

# 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 3ee70a1026)