{"record":{"id":"0005fcf9ab0e4cb0","repo":"tinyhumansai/openhuman","slug":"endpoint-must-start-with-http-or-https","errorCode":null,"errorMessage":"Endpoint must start with http:// or https://","messagePattern":"Endpoint must start with http:// or https://","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"app/src/components/settings/panels/AIPanel.tsx","lineNumber":2987,"sourceCode":"      // /models probe is made — auth + execution both go through the local\n      // `claude` CLI. Mirrors the Codex skip, but also skips model listing.\n      const isCliLogin = credentialMode === 'cli_login';\n      setBusyAction(`toggle-${localLabel ? localLabel.toLowerCase().replace(/\\s/g, '') : slug}`);\n      // A fresh attempt on THIS provider clears only its own prior advisory —\n      // an advisory about a different provider must survive (#5341).\n      setProviderSaveNotice(prev => (prev?.slug === slug ? null : prev));\n\n      try {\n        const trimmed = value.trim();\n        // For `endpoint_key` (OMLX), the endpoint URL arrives via `endpointOverride`\n        // (the dialog's endpoint field) and `trimmed` is the API key. For plain\n        // `endpoint` runtimes, `trimmed` itself is the endpoint URL.\n        const rawEndpoint = isEndpointKey ? (endpointOverride ?? '').trim() : trimmed;\n        const endpoint = isLocalRuntime\n          ? (() => {\n              const url = new URL(rawEndpoint);\n              if (!/^https?:$/.test(url.protocol)) {\n                throw new Error('Endpoint must start with http:// or https://');\n              }\n              if (url.pathname === '' || url.pathname === '/') {\n                url.pathname = '/v1';\n              }\n              return url.toString().replace(/\\/$/, '');\n            })()\n          : defaultEndpointFor(slug);\n\n        const upserted: CloudProvider = {\n          id: `p_${slug}_${Math.random().toString(36).slice(2, 7)}`,\n          slug,\n          label: localLabel ?? BUILTIN_PROVIDER_META[slug]?.label ?? slug,\n          endpoint,\n          authStyle: authStyleForSlug(slug),\n          // CLI-login providers hold no API key — reflect that honestly so\n          // the entry matches its reloaded (has_api_key === false) shape.\n          maskedKey: maskKeyLabel(!isCliLogin),\n        };","sourceCodeStart":2969,"sourceCodeEnd":3005,"githubUrl":"https://github.com/tinyhumansai/openhuman/blob/749120085864ce16e0f273c7b86fac7740b39c5b/app/src/components/settings/panels/AIPanel.tsx#L2969-L3005","documentation":"Thrown while saving a local-runtime provider in AI settings: the endpoint (endpointOverride for endpoint_key runtimes like OMLX, otherwise the trimmed field value) is parsed with new URL(rawEndpoint), and if it parses but the protocol is not http: or https:, this error fires. Note the ordering: a malformed URL (no scheme at all, e.g. 'localhost:1234' parsed as host 'localhost' with scheme mismatch, or 'foo bar') throws 'Invalid URL' from the URL constructor earlier - this specific message means a well-formed URL with the wrong scheme (file://, ws://, ftp://). On success an empty/'/' pathname is normalized to /v1 and a trailing slash is stripped.","triggerScenarios":"Pasting a URL with a non-HTTP scheme into the endpoint field of a local runtime (isLocalRuntime) - file:///path/to/model-server, ws://host:port, ftp://... - or a scheme typo that still parses as a URL.","commonSituations":"Copying a file path from Finder/Explorer and pasting it as the endpoint; users assuming 'localhost:8080' needs no scheme (that case throws Invalid URL from new URL instead, since localhost is treated as the scheme); pointing at a websocket endpoint of a server that also exposes HTTP.","solutions":["Use a full absolute URL with http:// or https://, e.g. http://127.0.0.1:8080 (the /v1 path is appended automatically if omitted)","For OMLX-style endpoint_key runtimes, put the URL in the dialog's endpoint field (endpointOverride), not in the API-key value","If you really meant a local file, that provider type is not a local runtime - use the non-local path where defaultEndpointFor(slug) applies"],"exampleFix":"// before\nconst rawEndpoint = 'file:///opt/models-server';\n\n// after\nconst rawEndpoint = 'http://127.0.0.1:8080'; // empty pathname normalizes to /v1","handlingStrategy":"validation","validationCode":"// Validate before the save handler runs\nfunction isValidHttpEndpoint(raw: string): boolean {\n  try {\n    const u = new URL(raw.trim());\n    return u.protocol === 'http:' || u.protocol === 'https:';\n  } catch {\n    return false; // no scheme / malformed - new URL() would throw Invalid URL\n  }\n}\n// disable Save: disabled={!isValidHttpEndpoint(endpointOverride ?? '')}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Always paste absolute URLs with an explicit http:// or https:// scheme - bare hosts fail earlier in new URL()","For local runtimes include the port, e.g. http://127.0.0.1:8080; /v1 is appended automatically when the path is empty","For OMLX-style endpoint_key runtimes, the URL belongs in the endpoint field, not the API-key field"],"tags":["ai-settings","provider","endpoint","url-validation","local-runtime"],"backgroundTag":"invalid-endpoint-url-scheme","analyzedSha":"749120085864ce16e0f273c7b86fac7740b39c5b","analyzedAt":"2026-08-17T21:21:45.363Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}