From 7b1871b99bd7d151617e85bf6c7082ec002c0fa4 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Tue, 21 Apr 2026 08:11:11 +0100 Subject: [PATCH] fix(browser): clarify DevToolsActivePort attach failures --- CHANGELOG.md | 1 + docs/gateway/troubleshooting.md | 1 + .../browser/server-context.availability.ts | 23 +++++++++++++++---- .../server-context.existing-session.test.ts | 22 ++++++++++++++++++ 4 files changed, 42 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9b984d8d0a7..106c4c19206 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,7 @@ Docs: https://docs.openclaw.ai - Anthropic/plugins: scope Anthropic `api: "anthropic-messages"` defaulting to Anthropic-owned providers, so `openai-codex` and other providers without an explicit `api` no longer get rewritten to the wrong transport. Fixes #64534. - fix(qqbot): add SSRF guard to direct-upload URL paths in uploadC2CMedia and uploadGroupMedia [AI-assisted]. (#69595) Thanks @pgondhi987. - fix(gateway): enforce allowRequestSessionKey gate on template-rendered mapping sessionKeys. (#69381) Thanks @pgondhi987. +- Browser/Chrome MCP: surface `DevToolsActivePort` attach failures as browser-connectivity errors instead of a generic "waiting for tabs" timeout, and point signed-out fallbacks toward the managed `openclaw` profile. - Webchat/images: treat inline image attachments as media for empty-turn gating while still ignoring metadata-only blank turns. (#69474) Thanks @Jaswir. - Discord/think: only show `adaptive` in `/think` autocomplete for provider/model pairs that actually support provider-managed adaptive thinking, so GPT/OpenAI models no longer advertise an Anthropic-only option. - Thinking: only expose `max` for models that explicitly support provider max reasoning, and remap stored `max` settings to the largest supported thinking mode when users switch to another model. diff --git a/docs/gateway/troubleshooting.md b/docs/gateway/troubleshooting.md index 8819aba67d8..06891e268a7 100644 --- a/docs/gateway/troubleshooting.md +++ b/docs/gateway/troubleshooting.md @@ -471,6 +471,7 @@ Common signatures: - `browser.executablePath not found` → configured path is invalid. - `browser.cdpUrl must be http(s) or ws(s)` → the configured CDP URL uses an unsupported scheme such as `file:` or `ftp:`. - `browser.cdpUrl has invalid port` → the configured CDP URL has a bad or out-of-range port. +- `Could not find DevToolsActivePort for chrome` → Chrome MCP existing-session could not attach to the selected browser data dir yet. Open the browser inspect page, enable remote debugging, keep the browser open, approve the first attach prompt, then retry. If signed-in state is not required, prefer the managed `openclaw` profile. - `No Chrome tabs found for profile="user"` → the Chrome MCP attach profile has no open local Chrome tabs. - `Remote CDP for profile "" is not reachable` → the configured remote CDP endpoint is not reachable from the gateway host. - `Browser attachOnly is enabled ... not reachable` or `Browser attachOnly is enabled and CDP websocket ... is not reachable` → attach-only profile has no reachable target, or the HTTP endpoint answered but the CDP WebSocket still could not be opened. diff --git a/extensions/browser/src/browser/server-context.availability.ts b/extensions/browser/src/browser/server-context.availability.ts index f4daa7e6eb3..541496bf402 100644 --- a/extensions/browser/src/browser/server-context.availability.ts +++ b/extensions/browser/src/browser/server-context.availability.ts @@ -123,6 +123,23 @@ export function createProfileAvailability({ }); }; + const formatChromeMcpAttachFailure = (lastError: unknown): string => { + const detail = lastError instanceof Error ? ` Last error: ${lastError.message}` : ""; + const message = lastError instanceof Error ? lastError.message : ""; + if (message.includes("DevToolsActivePort") || message.includes("Could not connect to Chrome")) { + return ( + `Chrome MCP existing-session attach for profile "${profile.name}" could not connect to Chrome. ` + + "Enable remote debugging in the browser inspect page, keep the browser open, approve the attach prompt, and retry. " + + 'If you do not need your signed-in browser session, use the managed "openclaw" profile instead.' + + detail + ); + } + return ( + `Chrome MCP existing-session attach for profile "${profile.name}" timed out waiting for tabs to become available.` + + ` Approve the browser attach prompt, keep the browser open, and retry.${detail}` + ); + }; + const reconcileProfileRuntime = async (): Promise => { const profileState = getProfileState(); const reconcile = profileState.reconcile; @@ -181,11 +198,7 @@ export function createProfileAvailability({ } await new Promise((r) => setTimeout(r, CHROME_MCP_ATTACH_READY_POLL_MS)); } - const detail = lastError instanceof Error ? ` Last error: ${lastError.message}` : ""; - throw new BrowserProfileUnavailableError( - `Chrome MCP existing-session attach for profile "${profile.name}" timed out waiting for tabs to become available.` + - ` Approve the browser attach prompt, keep the browser open, and retry.${detail}`, - ); + throw new BrowserProfileUnavailableError(formatChromeMcpAttachFailure(lastError)); }; const ensureBrowserAvailable = async (): Promise => { diff --git a/extensions/browser/src/browser/server-context.existing-session.test.ts b/extensions/browser/src/browser/server-context.existing-session.test.ts index bf693e8a82d..fc089ca1a5e 100644 --- a/extensions/browser/src/browser/server-context.existing-session.test.ts +++ b/extensions/browser/src/browser/server-context.existing-session.test.ts @@ -75,6 +75,7 @@ beforeEach(() => { afterEach(() => { vi.unstubAllEnvs(); + vi.useRealTimers(); }); describe("browser server-context existing-session profile", () => { @@ -134,4 +135,25 @@ describe("browser server-context existing-session profile", () => { ); expect(chromeMcp.closeChromeMcpSession).toHaveBeenCalledWith("chrome-live"); }); + + it("surfaces DevToolsActivePort attach failures instead of a generic tab timeout", async () => { + vi.useFakeTimers(); + fs.mkdirSync("/tmp/brave-profile", { recursive: true }); + vi.mocked(chromeMcp.listChromeMcpTabs).mockRejectedValue( + new Error( + "Could not connect to Chrome. Check if Chrome is running. Cause: Could not find DevToolsActivePort for chrome at /tmp/brave-profile/DevToolsActivePort", + ), + ); + + const state = makeState(); + const ctx = createBrowserRouteContext({ getState: () => state }); + const live = ctx.forProfile("chrome-live"); + + const pending = live.ensureBrowserAvailable(); + const assertion = expect(pending).rejects.toThrow( + /could not connect to Chrome.*managed "openclaw" profile.*DevToolsActivePort/s, + ); + await vi.advanceTimersByTimeAsync(8_000); + await assertion; + }); });