#9020: fix(browser): skip port ownership and WS reachability checks for remote CDP profiles
stale
Cluster:
Browser Enhancements and Fixes
# 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
#15595: fix(browser): avoid local port conflicts for remote cdp
by TsekaLuk · 2026-02-13
86.6%
#13568: Fix browser (OpenClaw-managed) launch fail by binding remote debugg...
by singlag · 2026-02-10
84.0%
#14944: fix(browser): prefer openclaw profile in headless/noSandbox environ...
by BenediktSchackenberg · 2026-02-12
82.3%
#6193: fix(browser): default to openclaw profile instead of chrome extensi...
by mikezaoldyeck · 2026-02-01
81.2%
#4663: fix: per-profile browser caching to allow parallel connections
by tsukhani · 2026-01-30
79.0%
#22952: fix(browser): suggest remote profile when local Chrome is not insta...
by dashed · 2026-02-21
78.8%
#19495: fix: handle Chrome process re-parenting on Windows
by andenwick · 2026-02-17
78.0%
#9728: fix(gateway): browser.snapshot ignores profile parameter (#9723)
by divol89 · 2026-02-05
77.7%
#7157: docs(browser): recommend openclaw managed profile as default
by AkashaBot · 2026-02-02
77.7%
#8282: Fix: Enable browser downloads via CDP Browser.setDownloadBehavior
by vishaltandale00 · 2026-02-03
76.7%