{"record":{"id":"2c9ff2e3eed35873","repo":"ZhuLinsen/daily_stock_analysis","slug":"hermes-base-url-contains-an-invalid-port","errorCode":null,"errorMessage":"Hermes BASE_URL contains an invalid port","messagePattern":"Hermes BASE_URL contains an invalid port","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"src/llm/hermes.py","lineNumber":193,"sourceCode":"        raise ValueError(\"Hermes BASE_URL must not include params, query, or fragment\")\n\n    raw_path = parsed.path or \"\"\n    decoded_path = unquote(raw_path)\n    if decoded_path not in {\"/v1\", \"/v1/\"}:\n        raise ValueError(\"Hermes BASE_URL path must be /v1\")\n    if quote(decoded_path, safe=\"/\") != raw_path.rstrip(\"/\") and raw_path not in {\"/v1\", \"/v1/\"}:\n        raise ValueError(\"Hermes BASE_URL path must not contain encoded segments\")\n\n    hostname = parsed.hostname.strip().lower()\n    if hostname == \"localhost\":\n        hostname = \"127.0.0.1\"\n    elif hostname not in {\"127.0.0.1\", \"::1\"}:\n        raise ValueError(\"Hermes BASE_URL must point to 127.0.0.1, localhost, or [::1]\")\n\n    try:\n        port = parsed.port\n    except ValueError as exc:\n        raise ValueError(\"Hermes BASE_URL contains an invalid port\") from exc\n\n    netloc = f\"[{hostname}]\" if \":\" in hostname else hostname\n    if port is not None:\n        netloc = f\"{netloc}:{port}\"\n    return urlunparse(parsed._replace(netloc=netloc, path=\"/v1\", params=\"\", query=\"\", fragment=\"\"))\n\n\ndef canonicalize_hermes_model_ref(raw_model: str) -> HermesModelRef:\n    \"\"\"Return the canonical DSA route and LiteLLM wire model for Hermes.\n\n    Hermes is OpenAI-compatible over local HTTP, so both route identity and\n    outbound wire model use LiteLLM's openai/ namespace.  The display label is\n    only UI metadata and must not be used for routing or provider detection.\n    \"\"\"\n\n    display_model = str(raw_model or \"\").strip() or HERMES_DEFAULT_MODEL\n    if display_model.startswith(\"openai/\"):\n        canonical = display_model","sourceCodeStart":175,"sourceCodeEnd":211,"githubUrl":"https://github.com/ZhuLinsen/daily_stock_analysis/blob/5159bd72e8373d215492dff122acc9d389e219c9/src/llm/hermes.py#L175-L211","documentation":"Raised when urlsplit's parsed.port property throws while canonicalizing the Hermes BASE_URL. Python's port accessor raises ValueError when the port segment is non-numeric or outside 0-65535; this code wraps it into a clearer Hermes-specific ValueError. It is a strict config-format error raised before any network activity.","triggerScenarios":"A BASE_URL like http://127.0.0.1:8080x/v1 (non-numeric port), http://127.0.0.1:99999/v1 (port above 65535), http://127.0.0.1:/v1 with stray characters, or a copy-paste artifact such as http://127.0.0.1:8080 /v1 that leaves a malformed netloc.","commonSituations":"Typos when hand-editing .env; port copied together with a colon or whitespace; using a URL with a service-port placeholder like <port> left unsubstituted; environment variable interpolation producing an empty-but-present malformed suffix.","solutions":["Correct the port to a plain integer between 1 and 65535, e.g. http://127.0.0.1:8080/v1","Remove stray characters, spaces, or trailing punctuation from the netloc portion of the URL","If the port comes from variable interpolation, print/render the final URL once during setup to catch substitution bugs","Omit the port entirely when Hermes listens on the default port 80 so the parser never touches a port segment"],"exampleFix":"# before\nHERMES_BASE_URL=http://127.0.0.1:8080x/v1\n\n# after\nHERMES_BASE_URL=http://127.0.0.1:8080/v1","handlingStrategy":"validation","validationCode":"from urllib.parse import urlsplit\n\ndef has_valid_port(url: str) -> bool:\n    try:\n        parsed = urlsplit(url)\n        parsed.port  # raises ValueError on bad port\n        return True\n    except ValueError:\n        return False","typeGuard":null,"tryCatchPattern":"try:\n    url = canonicalize_hermes_base_url(raw)\nexcept ValueError as exc:\n    if \"port\" in str(exc):\n        log.error(\"Fix HERMES_BASE_URL port: must be 1-65535\")\n    raise","preventionTips":["Smoke-test the full URL (urlsplit + .port access) in a config sanity check before the app starts","Generate URLs from host+port components instead of string concatenation","Render the final interpolated URL in debug logs during setup"],"tags":["config","llm","validation","url","port"],"backgroundTag":null,"analyzedSha":"5159bd72e8373d215492dff122acc9d389e219c9","analyzedAt":"2026-08-15T01:59:36.292Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}