musistudio/claude-code-router · error · Error

Only http and https URLs can be opened.

Error message

Only http and https URLs can be opened.

What it means

Thrown by normalizeExternalHttpUrl when asked to open a URL whose scheme is neither http: nor https:. The function parses the value with the URL constructor and whitelists only web schemes, deliberately blocking file:, javascript:, and other protocols.

Source

Thrown at packages/ui/src/web-client-bridge.ts:102

  }
}

function noopSubscription(): () => void {
  return () => undefined;
}

async function selectPluginDirectory(): Promise<unknown> {
  const directory = window.prompt("Plugin directory path");
  if (!directory?.trim()) {
    return undefined;
  }
  return rpc("selectPluginDirectory", [directory.trim()]);
}

function normalizeExternalHttpUrl(value: string): string {
  const url = new URL(value.trim());
  if (url.protocol !== "http:" && url.protocol !== "https:") {
    throw new Error("Only http and https URLs can be opened.");
  }
  return url.toString();
}

const webClientBridge: CcrApi = {
  applyClaudeAppGateway: (config) => rpc("applyClaudeAppGateway", [config]) as ReturnType<CcrApi["applyClaudeAppGateway"]>,
  applyProfile: () => rpc("applyProfile") as ReturnType<CcrApi["applyProfile"]>,
  cancelBotGatewayQrLogin: (request) => rpc("cancelBotGatewayQrLogin", [request]) as ReturnType<CcrApi["cancelBotGatewayQrLogin"]>,
  checkProviderConnectivity: (request) => rpc("checkProviderConnectivity", [request]) as ReturnType<CcrApi["checkProviderConnectivity"]>,
  clearProxyNetworkCaptures: () => rpc("clearProxyNetworkCaptures") as ReturnType<CcrApi["clearProxyNetworkCaptures"]>,
  closeBotGatewayQrWindow: (request) => rpc("closeBotGatewayQrWindow", [request]) as ReturnType<CcrApi["closeBotGatewayQrWindow"]>,
  closeTray: () => Promise.resolve(),
  detectProviderIcon: (request) => rpc("detectProviderIcon", [request]) as ReturnType<CcrApi["detectProviderIcon"]>,
  exportData: () => rpc("exportData") as ReturnType<CcrApi["exportData"]>,
  fetchProviderManifest: (request) => rpc("fetchProviderManifest", [request]) as ReturnType<CcrApi["fetchProviderManifest"]>,
  getAgentAnalysis: (filter) => rpc("getAgentAnalysis", [filter]) as ReturnType<CcrApi["getAgentAnalysis"]>,
  getAgentTracePayload: (request) => rpc("getAgentTracePayload", [request]) as ReturnType<CcrApi["getAgentTracePayload"]>,
  getAppInfo: () => rpc("getAppInfo") as ReturnType<CcrApi["getAppInfo"]>,

View on GitHub (pinned to 99f24806c6)

Solutions

  1. Use a full http(s) URL, e.g. https://example.com/page
  2. For local files, do not route them through this API — use the platform file-open mechanism instead
  3. Sanitize user input and reject non-http schemes before calling the bridge

Example fix

// before
openUrl("file:///home/user/report.html")

// after
openUrl("https://example.com/report.html")
Defensive patterns

Strategy: type-guard

Validate before calling

try { const u = new URL(value); if (u.protocol !== "http:" && u.protocol !== "https:") throw new Error("blocked"); } catch { /* reject before calling bridge */ }

Type guard

function isExternalHttpUrl(value: string): boolean {
  try { const u = new URL(value.trim()); return u.protocol === "http:" || u.protocol === "https:"; }
  catch { return false; }
}

Try / catch

try { openExternal(url); } catch (e) { if (e instanceof Error && e.message === "Only http and https URLs can be opened.") return; throw e; }

Prevention

When it happens

Trigger: Calling the open-URL bridge path with values like file:///home/user/doc.html, mailto:someone@example.com, about:blank, or a bare string that the URL constructor resolves to a non-http scheme.

Common situations: Passing local filesystem paths expecting them to open in a browser; user-supplied links from chat/config that use custom app schemes; Windows paths like C:\file.txt that parse as a drive-letter scheme.

Related errors


AI-assisted analysis of musistudio/claude-code-router@99f24806c6 (2026-08-27). Data as JSON: /api/errors/054de31c15da5549. Report an issue: GitHub.