From 070c31cdcfad0b718a55d760a0632f86c0e99a98 Mon Sep 17 00:00:00 2001 From: clawsweeper <274271284+clawsweeper[bot]@users.noreply.github.com> Date: Mon, 18 May 2026 18:10:12 +0000 Subject: [PATCH] fix(browser): preserve explicit cdpPort when cdpUrl omits port --- extensions/browser/src/browser/cdp.helpers.ts | 14 +++++--------- extensions/browser/src/browser/config.test.ts | 10 ++++------ 2 files changed, 9 insertions(+), 15 deletions(-) diff --git a/extensions/browser/src/browser/cdp.helpers.ts b/extensions/browser/src/browser/cdp.helpers.ts index 46c744572a7..25fd59a1767 100644 --- a/extensions/browser/src/browser/cdp.helpers.ts +++ b/extensions/browser/src/browser/cdp.helpers.ts @@ -28,17 +28,14 @@ export { isLoopbackHost }; function hasRawExplicitPort(raw: string): boolean { // Strip scheme (e.g. "http://") and take only the authority portion // (everything before the first /, ?, or #). - const authority = - raw - .replace(/^[a-z][a-z0-9+.-]*:\/\//i, "") - .split(/[/?#]/, 1)[0] ?? ""; + const authority = raw.replace(/^[a-z][a-z0-9+.-]*:\/\//i, "").split(/[/?#]/, 1)[0] ?? ""; - // Strip userinfo (user:pass@) — the colon there is NOT a port separator. + // Strip userinfo (user:pass@); the colon there is not a port separator. const hostPort = authority.includes("@") ? authority.slice(authority.lastIndexOf("@") + 1) : authority; - // IPv6: [::1]:9222 → port after closing bracket + // IPv6: [::1]:9222 has a port after the closing bracket. if (hostPort.startsWith("[")) { return /^\[[^\]]+\]:\d+$/.test(hostPort); } @@ -88,7 +85,7 @@ export function parseBrowserHttpUrl(raw: string, label: string) { const atIdx = rest.indexOf("@"); const hostStart = atIdx >= 0 ? atIdx + 1 : 0; const hostPart = rest.slice(hostStart); - // Find end of host: IPv6 brackets or first : / / + // Find the end of the host: IPv6 brackets, a path slash, or a port colon. const hostLen = hostPart.startsWith("[") ? hostPart.indexOf("]") + 1 : (() => { @@ -96,8 +93,7 @@ export function parseBrowserHttpUrl(raw: string, label: string) { return idx < 0 ? hostPart.length : idx; })(); const insertAt = hostStart + hostLen; - normalizedWithPort = - proto + rest.slice(0, insertAt) + ":" + port + rest.slice(insertAt); + normalizedWithPort = proto + rest.slice(0, insertAt) + ":" + port + rest.slice(insertAt); } else { normalizedWithPort = normalized; } diff --git a/extensions/browser/src/browser/config.test.ts b/extensions/browser/src/browser/config.test.ts index c80018cb166..d708439cd7c 100644 --- a/extensions/browser/src/browser/config.test.ts +++ b/extensions/browser/src/browser/config.test.ts @@ -621,9 +621,7 @@ describe("browser config", () => { const websocket = resolveProfile(resolved, "websocket"); expect(websocket?.cdpPort).toBe(443); - expect(websocket?.cdpUrl).toBe( - "wss://remote-browser.example.com:443/json/version?token=abc", - ); + expect(websocket?.cdpUrl).toBe("wss://remote-browser.example.com:443/json/version?token=abc"); const ipv6 = resolveProfile(resolved, "ipv6"); expect(ipv6?.cdpPort).toBe(80); @@ -716,9 +714,7 @@ describe("browser config", () => { }, }, }); - expect(() => resolveProfile(resolved, "bad")).toThrow( - 'must define cdpPort or cdpUrl', - ); + expect(() => resolveProfile(resolved, "bad")).toThrow("must define cdpPort or cdpUrl"); }); it("stale WS devtools URL + cdpPort drops path and uses cdpPort", () => { @@ -735,6 +731,8 @@ describe("browser config", () => { const profile = resolveProfile(resolved, "chrome-cdp"); expect(profile?.cdpUrl).toBe("http://127.0.0.1:9222"); expect(profile?.cdpPort).toBe(9222); + expect(profile?.cdpIsLoopback).toBe(true); + expect(profile?.attachOnly).toBe(true); }); it("IPv6 URL without port defers to cdpPort", () => {