{"record":{"id":"42dba1fe0e9661ca","repo":"musistudio/claude-code-router","slug":"provider-base-url-is-invalid","errorCode":null,"errorMessage":"Provider Base URL is invalid.","messagePattern":"Provider Base URL is invalid\\.","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/core/src/contracts/deep-link.ts","lineNumber":474,"sourceCode":"}\n\nfunction boundedString(value: string | undefined, maxLength: number, label: string): string | undefined {\n  if (!value) {\n    return undefined;\n  }\n  if (value.length > maxLength) {\n    throw new Error(`${label} is too long.`);\n  }\n  return value;\n}\n\nfunction validateProviderBaseUrl(value: string): void {\n  const url = new URL(providerUrlWithDefaultScheme(value));\n  if (![\"http:\", \"https:\"].includes(url.protocol)) {\n    throw new Error(\"Provider Base URL must use http or https.\");\n  }\n  if (!url.hostname) {\n    throw new Error(\"Provider Base URL is invalid.\");\n  }\n}\n\nfunction validateManifestUrl(value: string): void {\n  const url = new URL(value);\n  if (url.protocol !== \"https:\") {\n    throw new Error(\"Provider manifest URL must use https.\");\n  }\n  if (url.username || url.password) {\n    throw new Error(\"Provider manifest URL cannot include credentials.\");\n  }\n  if (!url.hostname) {\n    throw new Error(\"Provider manifest URL is invalid.\");\n  }\n}\n\nfunction normalizeProviderProtocol(value: string | undefined): GatewayProviderProtocol | undefined {\n  if (!value) {","sourceCodeStart":456,"sourceCodeEnd":492,"githubUrl":"https://github.com/musistudio/claude-code-router/blob/99f24806c6a2c660b16e53e95211c517448a6c90/packages/core/src/contracts/deep-link.ts#L456-L492","documentation":"After protocol validation, validateProviderBaseUrl requires a non-empty hostname. A URL like \"https:///v1\" or \"https://:8080\" parses but has no hostname, so the base URL points nowhere and is rejected as invalid.","triggerScenarios":"Passing base_url=\"https:///path\" (empty host), a URL consisting only of port/path, or one whose host was lost during templating.","commonSituations":"Template interpolation producing an empty host (https://${HOST}/v1 with HOST unset); copy-paste dropping the host; URLs built from config env vars that are undefined.","solutions":["Include a real hostname: https://api.provider.com/v1","If using env vars, ensure the host variable is set before building the URL","Log the final composed base_url before parsing to catch empty hosts early"],"exampleFix":"// before\n{\"base_url\":\"https:///v1\"}\n// after\n{\"base_url\":\"https://api.acme.dev/v1\"}","handlingStrategy":"validation","validationCode":"const u = new URL(baseUrl.includes(\"://\") ? baseUrl : `https://${baseUrl}`); if (!u.hostname) return reject(\"empty host\");","typeGuard":"const hasHostname = (v: string) => { try { return new URL(/^\\w+:\\/\\/.test(v) ? v : `https://${v}`).hostname.length > 0; } catch { return false; } };","tryCatchPattern":"try { validateProviderBaseUrl(baseUrl); } catch (e) { if (e instanceof Error && e.message === \"Provider Base URL is invalid.\") return promptForUrl(); throw e; }","preventionTips":["Fallback-interpolate hosts: `https://${host || defaultHost}/v1`","Log the final composed URL before parsing"],"tags":["base-url","validation","hostname"],"backgroundTag":"invalid-url-host","analyzedSha":"99f24806c6a2c660b16e53e95211c517448a6c90","analyzedAt":"2026-08-27T04:11:01.184Z","schemaVersion":2},"datasetVersion":"2026-08-27T08:17:20.692Z"}