From 8bccb0032ab525ecbc398aca2f1efdb46a9518de Mon Sep 17 00:00:00 2001 From: AaronWander Date: Sat, 28 Feb 2026 16:48:03 +0800 Subject: [PATCH] fix(browser): bound post-launch CDP wait by elapsed time (#21149) --- ...ure-browser-available.waits-for-cdp-ready.test.ts | 12 +++++++++++- src/browser/server-context.ts | 12 +++++++++--- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/src/browser/server-context.ensure-browser-available.waits-for-cdp-ready.test.ts b/src/browser/server-context.ensure-browser-available.waits-for-cdp-ready.test.ts index 92146de60b9..b16017e521b 100644 --- a/src/browser/server-context.ensure-browser-available.waits-for-cdp-ready.test.ts +++ b/src/browser/server-context.ensure-browser-available.waits-for-cdp-ready.test.ts @@ -1,8 +1,18 @@ import type { ChildProcessWithoutNullStreams } from "node:child_process"; import { EventEmitter } from "node:events"; import { afterEach, describe, expect, it, vi } from "vitest"; + +vi.mock("./chrome.js", () => ({ + isChromeCdpReady: vi.fn(async () => true), + isChromeReachable: vi.fn(async () => true), + launchOpenClawChrome: vi.fn(async () => { + throw new Error("unexpected launch"); + }), + resolveOpenClawUserDataDir: vi.fn(() => "/tmp/openclaw-test"), + stopOpenClawChrome: vi.fn(async () => {}), +})); + import * as chromeModule from "./chrome.js"; -import "./server-context.chrome-test-harness.js"; import type { BrowserServerState } from "./server-context.js"; import { createBrowserRouteContext } from "./server-context.js"; diff --git a/src/browser/server-context.ts b/src/browser/server-context.ts index 26e8ce5c38b..1890f56efcc 100644 --- a/src/browser/server-context.ts +++ b/src/browser/server-context.ts @@ -286,9 +286,15 @@ function createProfileContext( // launchOpenClawChrome() can return before Chrome is fully ready to serve /json/version + CDP WS. // If a follow-up call (snapshot/screenshot/etc.) races ahead, we can hit PortInUseError trying to // launch again on the same port. Poll briefly so browser(action="start"/"open") is stable. - const maxAttempts = 50; - for (let attempt = 0; attempt < maxAttempts; attempt++) { - if (await isReachable(1200)) { + // + // Bound the wait by wall-clock time to avoid long stalls when /json/version is reachable + // but the CDP WebSocket never becomes ready. + const deadlineMs = Date.now() + 8000; + while (Date.now() < deadlineMs) { + const remainingMs = Math.max(0, deadlineMs - Date.now()); + // Keep each attempt short; loopback profiles derive a WS timeout from this value. + const attemptTimeoutMs = Math.max(75, Math.min(250, remainingMs)); + if (await isReachable(attemptTimeoutMs)) { return; } await new Promise((r) => setTimeout(r, 100));