{"record":{"id":"807caaddf03f3a1b","repo":"headroomlabs-ai/headroom","slug":"proxyurl-must-use-http-or-https","errorCode":null,"errorMessage":"proxyUrl must use http:// or https://","messagePattern":"proxyUrl must use http:// or https://","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"plugins/openclaw/src/proxy-manager.ts","lineNumber":417,"sourceCode":"      return null;\n    }\n  }\n}\n\n/** Parse a URL, returning the parsed object or throwing a descriptive error. */\nfunction parseProxyUrl(proxyUrl: string): URL {\n  try {\n    return new URL(proxyUrl);\n  } catch {\n    throw new Error(`Invalid proxyUrl: \"${proxyUrl}\"`);\n  }\n}\n\nexport function normalizeAndValidateProxyUrl(proxyUrl: string): string {\n  const parsed = parseProxyUrl(proxyUrl);\n\n  if (parsed.protocol !== \"http:\" && parsed.protocol !== \"https:\") {\n    throw new Error(\"proxyUrl must use http:// or https://\");\n  }\n\n  if (parsed.pathname !== \"/\" || parsed.search || parsed.hash) {\n    throw new Error(\"proxyUrl must not include a path, query, or hash\");\n  }\n\n  return parsed.origin;\n}\n\n/** Returns true if the URL points to a local address (localhost or 127.0.0.1). */\nexport function isLocalProxyUrl(proxyUrl: string): boolean {\n  try {\n    const parsed = new URL(proxyUrl);\n    return parsed.hostname === \"127.0.0.1\" || parsed.hostname === \"localhost\";\n  } catch {\n    return false;\n  }\n}","sourceCodeStart":399,"sourceCodeEnd":435,"githubUrl":"https://github.com/headroomlabs-ai/headroom/blob/322425c43bffde1ed0b64fecf3cf5951565dd82b/plugins/openclaw/src/proxy-manager.ts#L399-L435","documentation":"The proxyUrl parsed successfully but its scheme is neither http: nor https: — Headroom routing only works over plain HTTP(S) proxies, so schemes like ws://, tcp:, or unix: are rejected upfront. This fires after parseProxyUrl succeeds but before path/query checks.","triggerScenarios":"Passing proxyUrl values such as \"ws://127.0.0.1:8787\", \"socks5://localhost:8787\", \"tcp://10.0.0.1:8787\", or \"file:///...\" to normalizeAndValidateProxyUrl().","commonSituations":"Reusing a WebSocket endpoint URL from other Headroom/OpenClaw tooling as proxyUrl; confusion between the proxy's HTTP port and its WS/gRPC port; template variable that injects the wrong scheme.","solutions":["Use the proxy's HTTP(S) endpoint, e.g. \"http://127.0.0.1:8787\"","If the deployment fronts Headroom with TLS, use https:// with the public origin","Check the config template/env var that supplied the scheme (ws:// vs http:// mixups are the most common)"],"exampleFix":"// before\nmanager.configure({ proxyUrl: \"ws://127.0.0.1:8787\" }); // throws\n\n// after\nmanager.configure({ proxyUrl: \"http://127.0.0.1:8787\" });","handlingStrategy":"type-guard","validationCode":"function usesHttpScheme(value: string): boolean {\n  try {\n    return [\"http:\", \"https:\"].includes(new URL(value).protocol);\n  } catch {\n    return false;\n  }\n}\n\nif (!usesHttpScheme(proxyUrl)) {\n  throw new Error(`proxyUrl must use http:// or https:// (got ${proxyUrl})`);","typeGuard":"function isHttpProxyUrl(value: string): value is string {\n  try {\n    return new URL(value).protocol === \"http:\" || new URL(value).protocol === \"https:\";\n  } catch {\n    return false;\n  }\n}","tryCatchPattern":null,"preventionTips":["Never reuse ws:// or grpc:// endpoint URLs as proxyUrl — the Headroom proxy is HTTP(S) only","Centralize the scheme choice in one config key to avoid template-level ws/http mixups","Validate proxyUrl with the library's own normalizeAndValidateProxyUrl() during startup"],"tags":["proxy","url","validation","openclaw"],"backgroundTag":null,"analyzedSha":"322425c43bffde1ed0b64fecf3cf5951565dd82b","analyzedAt":"2026-08-15T01:03:05.481Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}