ZhuLinsen/daily_stock_analysis · error · ValueError

Hermes BASE_URL must include a loopback host

Error message

Hermes BASE_URL must include a loopback host

What it means

Host-presence check in canonicalize_hermes_base_url: after scheme validation, the URL must have a netloc and a parseable hostname. URLs like 'http://' (empty authority), 'http:///v1' (empty netloc), or 'http://:8642/v1' (port but no host) fail here. Note the message says 'loopback host' because the immediately following checks enforce loopback — this check only verifies a host exists at all.

Source

Thrown at src/llm/hermes.py:171

    normalized = (protocol or HERMES_DEFAULT_PROTOCOL).strip().lower() or HERMES_DEFAULT_PROTOCOL
    if normalized != HERMES_DEFAULT_PROTOCOL:
        raise ValueError("Hermes only supports PROTOCOL=openai")
    return HERMES_DEFAULT_PROTOCOL


def canonicalize_hermes_base_url(base_url: str) -> str:
    """Return canonical Hermes base URL or raise ValueError.

    Allowed forms are loopback HTTP(S) URLs whose path is exactly /v1 or /v1/.
    localhost is canonicalized to 127.0.0.1 to avoid DNS/hosts ambiguity.
    """

    raw = (base_url or HERMES_DEFAULT_BASE_URL).strip() or HERMES_DEFAULT_BASE_URL
    parsed = urlparse(raw)
    if parsed.scheme.lower() not in {"http", "https"}:
        raise ValueError("Hermes BASE_URL must use http or https")
    if not parsed.netloc or not parsed.hostname:
        raise ValueError("Hermes BASE_URL must include a loopback host")
    if parsed.username or parsed.password:
        raise ValueError("Hermes BASE_URL must not include userinfo")
    if parsed.params or parsed.query or parsed.fragment:
        raise ValueError("Hermes BASE_URL must not include params, query, or fragment")

    raw_path = parsed.path or ""
    decoded_path = unquote(raw_path)
    if decoded_path not in {"/v1", "/v1/"}:
        raise ValueError("Hermes BASE_URL path must be /v1")
    if quote(decoded_path, safe="/") != raw_path.rstrip("/") and raw_path not in {"/v1", "/v1/"}:
        raise ValueError("Hermes BASE_URL path must not contain encoded segments")

    hostname = parsed.hostname.strip().lower()
    if hostname == "localhost":
        hostname = "127.0.0.1"
    elif hostname not in {"127.0.0.1", "::1"}:
        raise ValueError("Hermes BASE_URL must point to 127.0.0.1, localhost, or [::1]")

View on GitHub (pinned to 5159bd72e8)

Solutions

  1. Set a complete URL with an explicit loopback host: http://127.0.0.1:8642/v1 (localhost and [::1] are also accepted and canonicalized).
  2. Check for unset/empty variables used inside BASE_URL templates.

Example fix

# before
BASE_URL=http:///v1

# after
BASE_URL=http://127.0.0.1:8642/v1
Defensive patterns

Strategy: validation

Validate before calling

parsed = urlparse(base_url)
assert parsed.netloc and parsed.hostname, "BASE_URL needs a host"

Type guard

def url_has_host(value: str) -> bool:
    p = urlparse(value)
    return bool(p.netloc and p.hostname)

Try / catch

try:
    url = canonicalize_hermes_base_url(cfg.base_url)
except ValueError as exc:
    raise ConfigError(str(exc)) from exc

Prevention

When it happens

Trigger: BASE_URL='http://' or 'http:///v1'; a URL where the host got dropped by templating/env substitution leaving an empty value mid-URL.

Common situations: Env var interpolation producing 'http://${HOST}:8642/v1' with HOST unset in some shells; truncated copy-paste of the URL.

Related errors


AI-assisted analysis of ZhuLinsen/daily_stock_analysis@5159bd72e8 (2026-08-15). Data as JSON: /api/errors/7dc12c78ca046937. Report an issue: GitHub.