{"record":{"id":"a7879f0a7f145354","repo":"headroomlabs-ai/headroom","slug":"proxyurl-must-not-include-a-path-query-or-hash","errorCode":null,"errorMessage":"proxyUrl must not include a path, query, or hash","messagePattern":"proxyUrl must not include a path, query, or hash","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"plugins/openclaw/src/proxy-manager.ts","lineNumber":421,"sourceCode":"\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}\n\nfunction withDefaultPort(proxyUrl: string, defaultPort: number): string {\n  const parsed = parseProxyUrl(proxyUrl);\n  if (!parsed.port) {","sourceCodeStart":403,"sourceCodeEnd":439,"githubUrl":"https://github.com/headroomlabs-ai/headroom/blob/322425c43bffde1ed0b64fecf3cf5951565dd82b/plugins/openclaw/src/proxy-manager.ts#L403-L439","documentation":"The proxyUrl is a valid http(s) URL but carries a path, query string, or fragment (anything where pathname !== '/', or search/hash present). Headroom proxy routing is origin-based; per-request paths cannot be encoded in proxyUrl, so the validator rejects it to avoid silently dropping those components.","triggerScenarios":"Passing values like \"http://127.0.0.1:8787/headroom\", \"http://host:8787/?token=abc\", or \"http://host:8787/#fragment\" to normalizeAndValidateProxyUrl(). A trailing slash is fine (pathname '/'), any deeper path is not.","commonSituations":"Copy-pasting a dashboard or WS path from Headroom docs into proxyUrl; appending an auth token as a query param; reverse-proxy deployments where users try to include the mounted sub-path.","solutions":["Strip the path/query/hash and pass only the origin: \"http://127.0.0.1:8787\"","If auth is needed for the proxy, use the mechanism Headroom actually supports (e.g. headers/env config), not URL query params","If Headroom sits behind a reverse proxy sub-path, expose it at a dedicated origin/port instead"],"exampleFix":"// before\nmanager.configure({ proxyUrl: \"http://127.0.0.1:8787/proxy?token=abc\" }); // throws\n\n// after\nmanager.configure({ proxyUrl: \"http://127.0.0.1:8787\" });","handlingStrategy":"type-guard","validationCode":"function isBareOrigin(value: string): boolean {\n  try {\n    const u = new URL(value);\n    return u.pathname === \"/\" && !u.search && !u.hash;\n  } catch {\n    return false;\n  }\n}\n\nif (!isBareOrigin(proxyUrl)) {\n  throw new Error(`proxyUrl must be an origin only (no path/query/hash): got ${proxyUrl}`);","typeGuard":"function isOriginOnlyUrl(value: string): value is string {\n  try {\n    const u = new URL(value);\n    return u.pathname === \"/\" && !u.search && !u.hash;\n  } catch {\n    return false;\n  }\n}","tryCatchPattern":null,"preventionTips":["Pass only the origin (scheme://host:port) as proxyUrl; a single trailing slash is the only allowed extra","Do not encode proxy credentials or tokens in the URL query — use the supported auth mechanism","If Headroom sits behind a sub-path reverse proxy, expose it at its own origin instead of encoding the path"],"tags":["proxy","url","validation","openclaw"],"backgroundTag":null,"analyzedSha":"322425c43bffde1ed0b64fecf3cf5951565dd82b","analyzedAt":"2026-08-15T01:03:05.481Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}