← Back to PRs

#9020: fix(browser): skip port ownership and WS reachability checks for remote CDP profiles

by yepack open 2026-02-04 19:20 View on GitHub →
stale
# fix(browser): skip port ownership and WS reachability checks for remote CDP profiles ## Problem OpenClaw fails to connect to external CDP servers (self-hosted Browserless v2) with: ``` Port XXXX is in use for profile "profilename" but not by openclaw ``` or after patching the first check: ``` Remote CDP websocket for profile "profilename" is not reachable. ``` This happens because: 1. **Port ownership check**: OpenClaw checks if it "owns" the process on the CDP port — always fails for external services 2. **WS reachability check**: Browserless v2 (and similar) create sessions on-demand via WebSocket — there's no persistent WS endpoint to probe ## File Changed `src/browser/server-context.ts` (compiled: `dist/browser/server-context.js`) ## Changes (3 hunks) ### Change 1: Skip port ownership check for remote CDP The port ownership check (`!profileState.running`) should not apply to remote CDP profiles — the port is managed by an external service, not OpenClaw. **Before:** ```js // HTTP responds but WebSocket fails - port in use by something else if (!profileState.running) { throw new Error(`Port ${profile.cdpPort} is in use for profile "${profile.name}" but not by openclaw. ` + `Run action=reset-profile profile=${profile.name} to kill the process.`); } ``` **After:** ```js // HTTP responds but WebSocket fails - port in use by something else if (!profileState.running && !remoteCdp) { throw new Error(`Port ${profile.cdpPort} is in use for profile "${profile.name}" but not by openclaw. ` + `Run action=reset-profile profile=${profile.name} to kill the process.`); } ``` ### Change 2 & 3: Skip WS reachability error for remote CDP For remote CDP services like Browserless v2 that create browser sessions on-demand (per WebSocket connection), the WS reachability probe will always fail because there's no persistent browser instance. The check should return early instead of throwing. **Before:** ```js if (current.resolved.attachOnly || remoteCdp) { if (opts.onEnsureAttachTarget) { await opts.onEnsureAttachTarget(profile); if (await isReachable(1200)) { return; } } throw new Error(remoteCdp ? `Remote CDP websocket for profile "${profile.name}" is not reachable.` : `Browser attachOnly is enabled and CDP websocket for profile "${profile.name}" is not reachable.`); } ``` **After:** ```js if (current.resolved.attachOnly || remoteCdp) { if (opts.onEnsureAttachTarget) { await opts.onEnsureAttachTarget(profile); if (await isReachable(1200)) { return; } } if (remoteCdp) return; // Remote CDP (e.g. Browserless v2): sessions created on-demand throw new Error(remoteCdp ? `Remote CDP websocket for profile "${profile.name}" is not reachable.` : `Browser attachOnly is enabled and CDP websocket for profile "${profile.name}" is not reachable.`); } ``` ## Summary of all changes | # | Line (approx) | Change | Reason | |---|---|---|---| | 1 | ~245 | Add `if (remoteCdp) return;` before WS error throw | On-demand WS sessions (Browserless v2) | | 2 | ~258 | `!profileState.running` → `!profileState.running && !remoteCdp` | Skip port ownership for remote | | 3 | ~271 | Add `if (remoteCdp) return;` before WS error throw | Same as table's 1 point, second occurrence | ## Testing Tested with: - **Browserless v2** (`ghcr.io/browserless/chromium:latest`) self-hosted in Docker - **OpenClaw 2026.2.1** on macOS (Apple Silicon) - Config: ```json { "browser": { "enabled": true, "defaultProfile": "browserless", "profiles": { "browserless": { "cdpUrl": "http://192.168.x.x:19222", "color": "#00AA00" } } } } ``` ### Browserless v2 Docker ```bash docker run -d \ --name browserless \ -p 19222:3000 \ -e "TIMEOUT=300000" \ -e "CONCURRENT=10" \ ghcr.io/browserless/chromium:latest ``` ### Before fix ``` Error: Port 19222 is in use for profile "browserless" but not by openclaw. ``` ### After fix ``` opened: https://example.com/ id: 285999EBCA1FCBDB60BABF704E8F65ED ``` All browser operations work: `open`, `tabs`, `snapshot`, `navigate`, `screenshot`. ## Cloud CDP services This fix also enables cloud-hosted CDP services like Browserless Cloud. Per [OpenClaw docs](https://docs.openclaw.ai), token auth is passed via `cdpUrl` query parameter: ```json { "browser": { "profiles": { "browserless": { "cdpUrl": "https://production-sfo.browserless.io?token=<BROWSERLESS_API_KEY>" } } } } ``` **_Not tested with cloud endpoints in this PR — only self-hosted Browserless v2 without TOKEN._** <!-- greptile_comment --> <h2>Greptile Overview</h2> <h3>Greptile Summary</h3> This PR adjusts CDP connectivity checks in `src/browser/server-context.ts` to better support remote CDP providers (e.g., Browserless) by (a) skipping the local port-ownership check when the profile is remote, and (b) bypassing the WebSocket reachability probe for remote CDP/attach-only flows where sessions may be created on demand. The changes are concentrated in `createProfileContext().ensureBrowserAvailable()`, which gates browser startup/attach behavior before tab operations (`listTabs`, `openTab`, etc.) proceed. <h3>Confidence Score: 3/5</h3> - This PR is close to mergeable but has one behavior change that can hide real remote connectivity failures. - Skipping WS probes for remote CDP is reasonable, but the new early return also bypasses the HTTP reachability error path for remote profiles when the service is down/misconfigured, leading to confusing downstream failures. - src/browser/server-context.ts <!-- greptile_other_comments_section --> <sub>(2/5) Greptile learns from your feedback when you react with thumbs up/down!</sub> **Context used:** - Context from `dashboard` - CLAUDE.md ([source](https://app.greptile.com/review/custom-context?memory=fd949e91-5c3a-4ab5-90a1-cbe184fd6ce8)) - Context from `dashboard` - AGENTS.md ([source](https://app.greptile.com/review/custom-context?memory=0d0c8278-ef8e-4d6c-ab21-f5527e322f13)) <!-- /greptile_comment -->

Most Similar PRs