can1357/oh-my-pi · error · ToolError

browser app.cdp_url must be the HTTP CDP discovery endpoint

Error message

browser app.cdp_url must be the HTTP CDP discovery endpoint (for example http://127.0.0.1:9222), not a ws:// browser websocket URL.

What it means

normalizeConnectedCdpUrl canonicalizes a user-supplied CDP URL from the browser app config and rejects ws:// or wss:// URLs. Chrome exposes two different endpoints: an HTTP discovery endpoint (e.g. http://127.0.0.1:9222) used with puppeteer's browserURL, and a raw browser websocket URL. This library requires the HTTP discovery endpoint and throws ToolError when given a websocket URL instead.

Source

Thrown at packages/coding-agent/src/tools/browser/registry.ts:157

		// orphaned Chromium/app process / puppeteer handle survives to process
		// exit. (Issue #3963.)
		if (opts.signal?.aborted) {
			await disposeBrowserHandle(handle, { kill: kind.kind === "spawned" }).catch(err => {
				logger.debug("Failed to dispose orphan browser after abort", {
					error: err instanceof Error ? err.message : String(err),
				});
			});
			throw new ToolAbortError("Browser open aborted");
		}
		browsers.set(key, handle);
		return handle;
	}
}

export function normalizeConnectedCdpUrl(rawCdpUrl: string): string {
	const cdpUrl = rawCdpUrl.replace(/\/+$/, "");
	if (/^wss?:\/\//i.test(cdpUrl)) {
		throw new ToolError(
			"browser app.cdp_url must be the HTTP CDP discovery endpoint (for example http://127.0.0.1:9222), not a ws:// browser websocket URL.",
		);
	}
	return cdpUrl;
}

async function openBrowserHandle(kind: BrowserKind, opts: AcquireBrowserOptions): Promise<BrowserHandle> {
	if (kind.kind === "cmux") {
		const client = new CmuxSocketClient({ socketPath: kind.socketPath, password: kind.password });
		await client.connect();
		return {
			key: browserKey(kind),
			kind,
			client,
			surface: kind.surface,
			refCount: 0,
		};
	}

View on GitHub (pinned to 9690622007)

Solutions

  1. Change app.cdp_url to the HTTP discovery endpoint, e.g. http://127.0.0.1:9222 (strip the /devtools/browser/... path entirely)
  2. If the browser runs on another host/port, point to http://<host>:<port> where the /json/version endpoint responds
  3. Restart the browser with --remote-debugging-port=<port> if no HTTP endpoint exists

Example fix

// before
{ "cdp_url": "ws://127.0.0.1:9222/devtools/browser/8f6a-..." }
// after
{ "cdp_url": "http://127.0.0.1:9222" }
Defensive patterns

Strategy: validation

Validate before calling

function assertHttpCdpUrl(raw: string): string {
  const trimmed = raw.replace(/\/+$/, "");
  if (!/^https?:\/\//i.test(trimmed)) {
    throw new Error(`cdp_url must start with http:// or https:// (got ${raw})`);
  }
  return trimmed;
}

Type guard

function isHttpCdpUrl(raw: string): boolean {
  return /^https?:\/\//i.test(raw.trim());
}

Prevention

When it happens

Trigger: Setting browser app config cdp_url to a ws://... value, typically copied from the 'webSocketDebuggerUrl' field of /json/version output, or from a remote-debugging guide that shows the websocket URL.

Common situations: Users pasting the DevTools websocket URL from chrome://version or /json/version; misconfigured CI browser services that export ws endpoints; mixing up puppeteer.connect({browserWSEndpoint}) semantics with this tool's browserURL semantics.

Related errors


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/fcf58d460e2adde0. Report an issue: GitHub.