headroomlabs-ai/headroom · error · Error

proxyUrl must not include a path, query, or hash

Error message

proxyUrl must not include a path, query, or hash

What it means

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.

Source

Thrown at plugins/openclaw/src/proxy-manager.ts:421

/** Parse a URL, returning the parsed object or throwing a descriptive error. */
function parseProxyUrl(proxyUrl: string): URL {
  try {
    return new URL(proxyUrl);
  } catch {
    throw new Error(`Invalid proxyUrl: "${proxyUrl}"`);
  }
}

export function normalizeAndValidateProxyUrl(proxyUrl: string): string {
  const parsed = parseProxyUrl(proxyUrl);

  if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
    throw new Error("proxyUrl must use http:// or https://");
  }

  if (parsed.pathname !== "/" || parsed.search || parsed.hash) {
    throw new Error("proxyUrl must not include a path, query, or hash");
  }

  return parsed.origin;
}

/** Returns true if the URL points to a local address (localhost or 127.0.0.1). */
export function isLocalProxyUrl(proxyUrl: string): boolean {
  try {
    const parsed = new URL(proxyUrl);
    return parsed.hostname === "127.0.0.1" || parsed.hostname === "localhost";
  } catch {
    return false;
  }
}

function withDefaultPort(proxyUrl: string, defaultPort: number): string {
  const parsed = parseProxyUrl(proxyUrl);
  if (!parsed.port) {

View on GitHub (pinned to 322425c43b)

Solutions

  1. Strip the path/query/hash and pass only the origin: "http://127.0.0.1:8787"
  2. If auth is needed for the proxy, use the mechanism Headroom actually supports (e.g. headers/env config), not URL query params
  3. If Headroom sits behind a reverse proxy sub-path, expose it at a dedicated origin/port instead

Example fix

// before
manager.configure({ proxyUrl: "http://127.0.0.1:8787/proxy?token=abc" }); // throws

// after
manager.configure({ proxyUrl: "http://127.0.0.1:8787" });
Defensive patterns

Strategy: type-guard

Validate before calling

function isBareOrigin(value: string): boolean {
  try {
    const u = new URL(value);
    return u.pathname === "/" && !u.search && !u.hash;
  } catch {
    return false;
  }
}

if (!isBareOrigin(proxyUrl)) {
  throw new Error(`proxyUrl must be an origin only (no path/query/hash): got ${proxyUrl}`);

Type guard

function isOriginOnlyUrl(value: string): value is string {
  try {
    const u = new URL(value);
    return u.pathname === "/" && !u.search && !u.hash;
  } catch {
    return false;
  }
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of headroomlabs-ai/headroom@322425c43b (2026-08-15). Data as JSON: /api/errors/a7879f0a7f145354. Report an issue: GitHub.