NousResearch/hermes-agent · error · RuntimeError

Malformed custom endpoint URL: {candidate!r}. Run `hermes se

Error message

Malformed custom endpoint URL: {candidate!r}. Run `hermes setup` or `hermes model` and enter a valid http(s) base URL.

What it means

Pre-flight validation of a custom endpoint base_url in _validate_base_url: any non-empty candidate that is not an acp:// URL must parse with an http/https scheme and a valid port, else urlparse(...).port raises ValueError. Hermes rejects obviously broken URLs before they reach httpx and points the user back to the setup/model wizard as the sanctioned way to change it.

Source

Thrown at agent/auxiliary_client.py:3549

            raise RuntimeError(
                f"Malformed proxy environment variable {key}={value!r}. "
                "Fix or unset your proxy settings and try again."
            ) from exc


def _validate_base_url(base_url: str) -> None:
    """Reject obviously broken custom endpoint URLs before they reach httpx."""
    from urllib.parse import urlparse

    candidate = str(base_url or "").strip()
    if not candidate or candidate.startswith("acp://"):
        return
    try:
        parsed = urlparse(candidate)
        if parsed.scheme in {"http", "https"}:
            _ = parsed.port              # raises ValueError for malformed ports
    except ValueError as exc:
        raise RuntimeError(
            f"Malformed custom endpoint URL: {candidate!r}. "
            "Run `hermes setup` or `hermes model` and enter a valid http(s) base URL."
        ) from exc


def _try_custom_endpoint() -> Tuple[Optional[Any], Optional[str]]:
    runtime = _resolve_custom_runtime()
    if len(runtime) == 2:
        custom_base, custom_key = runtime
        custom_mode = None
    else:
        custom_base, custom_key, custom_mode = runtime
    if not custom_base or not custom_key:
        return None, None
    if custom_base.lower().startswith(_CODEX_AUX_BASE_URL.lower()):
        return None, None
    model = _read_main_model_for_aux() or "gpt-4o-mini"
    logger.debug("Auxiliary client: custom endpoint (%s, api_mode=%s)", model, custom_mode or "chat_completions")

View on GitHub (pinned to c896c09c42)

Solutions

  1. Run `hermes setup` or `hermes model` and re-enter a valid http(s) URL such as https://api.example.com/v1
  2. Or hand-fix model.base_url in config.yaml, ensuring a proper scheme and, if present, a numeric port
  3. Verify it parses: python -c "from urllib.parse import urlparse; urlparse('YOUR_URL').port"

Example fix

# config.yaml — before
model:
  base_url: "https://api.example.com:v1"

# after
model:
  base_url: "https://api.example.com/v1"
Defensive patterns

Strategy: validation

Validate before calling

from urllib.parse import urlparse

def base_url_valid(base_url: str) -> bool:
    candidate = str(base_url or "").strip()
    if not candidate or candidate.startswith("acp://"):
        return True
    try:
        parsed = urlparse(candidate)
        if parsed.scheme in {"http", "https"}:
            _ = parsed.port
        return parsed.scheme in {"http", "https"}
    except ValueError:
        return False

if not base_url_valid(configured_base_url):
    reject_config("model.base_url", configured_base_url)

Try / catch

try:
    client = _try_custom_endpoint()
except RuntimeError as e:
    if "Malformed custom endpoint URL" in str(e):
        reopen_model_wizard()  # hermes setup / hermes model re-prompts for the URL
    else:
        raise

Prevention

When it happens

Trigger: model.base_url (custom endpoint) in config.yaml like 'https://api.example.com:v1' — a host:port segment where the port is not numeric — so parsing raises during _try_custom_endpoint validation.

Common situations: Hand-edited config.yaml with a typo'd URL; a pasted URL carrying trailing garbage into the port field; port written as '8080/' after a bad search-replace.

Understand the failure class

Related errors


AI-assisted analysis of NousResearch/hermes-agent@c896c09c42 (2026-08-14). Data as JSON: /api/errors/1a7dcd6dd1697996. Report an issue: GitHub.