docs/tools/browser-wsl2-windows-remote-cdp-troubleshooting.md
In the common split-host setup, OpenClaw Gateway runs inside WSL2, Chrome runs on Windows, and browser control must cross the WSL2/Windows boundary. Several independent problems can surface at once (see issue #39369): CDP transport, Control UI origin security, and token/pairing can each fail on their own while producing similar-looking errors. Work through the layers below in order instead of guessing which one is broken.
Use a remote browser profile pointing from WSL2 to a Windows Chrome CDP endpoint. Choose this when the Gateway stays inside WSL2, Chrome runs on Windows, and browser control needs to cross the WSL2/Windows boundary.
Use the existing-session driver (user profile) only when the Gateway runs
on the same host as Chrome, you want the local signed-in browser state, you do
not need cross-host browser transport, and you do not need responsebody,
PDF export, download interception, or batch actions (Chrome MCP profiles do
not support these).
For WSL2 Gateway + Windows Chrome, use raw remote CDP. Chrome MCP is host-local, not a WSL2-to-Windows bridge.
127.0.0.1:18789http://127.0.0.1:18789/9222When the UI is opened from Windows, use Windows localhost unless you have a deliberate HTTPS setup:
http://127.0.0.1:18789/
Do not default to a LAN IP. Plain HTTP on a LAN or tailnet address can trigger insecure-origin/device-auth behavior unrelated to CDP itself. See Control UI.
Work top to bottom; do not skip ahead. Fixing one layer can still leave a different error visible from a layer further down.
chrome.exe --remote-debugging-port=9222 --user-data-dir="$env:LOCALAPPDATA\OpenClaw\ChromeCDP"
Chrome 136 and later ignore remote-debugging command-line switches for the default Chrome data directory. Use a separate, non-default data directory as shown above. See Chrome's remote-debugging security change. This does not make the normal signed-in Chrome profile remotely controllable.
From Windows, verify Chrome itself first:
curl.exe http://127.0.0.1:9222/json/version
curl.exe http://127.0.0.1:9222/json/list
If this fails, diagnose the Windows listeners below. OpenClaw is not the problem yet.
Chromium tries to bind remote debugging to 127.0.0.1 first and falls back to
[::1] only if the IPv4 bind fails. A persistent v4tov4 rule listening on
127.0.0.1:9222 can occupy that endpoint before Chrome starts. Chrome then
falls back to [::1]:9222, while the old rule forwards IPv4 traffic back to
its own listener and returns an empty reply.
Check the actual listeners and proxy rules from Windows instead of inferring them from the Chrome version:
netstat -ano | findstr :9222
netsh interface portproxy show all
curl.exe http://127.0.0.1:9222/json/version
curl.exe http://[::1]:9222/json/version
Use tasklist /fi "PID eq <PID>" for each PID from netstat.
If chrome.exe answers on 127.0.0.1, remove any portproxy rule that also
listens on 127.0.0.1:9222. Forward only the WSL2-reachable Windows adapter
address to 127.0.0.1.
If chrome.exe answers only on [::1], point the WSL2-reachable listener at
::1 with v4tov6 instead of forwarding to an unused IPv4 address:
netsh interface portproxy add v4tov6 listenaddress=WINDOWS_HOST_OR_IP listenport=9222 connectaddress=::1 connectport=9222
Bind the listener to the adapter address WSL2 needs. Do not expose the CDP
port on 0.0.0.0, a LAN address, or a tailnet address: CDP grants control of
the browser session.
From WSL2, test the exact address you plan to use in cdpUrl:
curl http://WINDOWS_HOST_OR_IP:9222/json/version
curl http://WINDOWS_HOST_OR_IP:9222/json/list
Good result:
/json/version returns JSON with Browser / Protocol-Version metadata/json/list returns JSON (an empty array is fine if no pages are open)If this fails, Windows is not exposing the port to WSL2 yet, the address is wrong for the WSL2 side, or firewall/port-forwarding/proxying is missing. Fix that before touching OpenClaw config.
Point OpenClaw at the address reachable from WSL2:
{
browser: {
enabled: true,
defaultProfile: "remote",
profiles: {
remote: {
cdpUrl: "http://WINDOWS_HOST_OR_IP:9222",
attachOnly: true,
color: "#00AA00",
},
},
},
}
Notes:
attachOnly: true for externally managed browserscdpUrl can be http://, https://, ws://, or wss:///json/versioncurl before expecting OpenClaw to succeedOpen http://127.0.0.1:18789/ from Windows, then verify:
gateway.controlUi.allowedOrigins expectsHelpful page: Control UI.
From WSL2:
openclaw browser --browser-profile remote open https://example.com
openclaw browser --browser-profile remote tabs
Good result:
browser tabs returns the targetsnapshot, screenshot, navigate) work from the same
profile| Message | Meaning |
|---|---|
control-ui-insecure-auth | UI origin/secure-context problem, not a CDP transport problem |
token_missing | auth configuration problem |
pairing required | device approval problem |
Remote CDP for profile "remote" is not reachable | WSL2 cannot reach the configured cdpUrl |
empty CDP reply / other side closed through a portproxy | Windows listener mismatch or a self-loop; inspect both loopback families and netsh interface portproxy show all |
Browser attachOnly is enabled and CDP websocket for profile "remote" is not reachable | the HTTP endpoint answered, but the DevTools WebSocket could not be opened |
| stale viewport / dark-mode / locale / offline overrides after a remote session | run openclaw browser --browser-profile remote stop to close the session and release the cached Playwright/CDP connection without restarting the Gateway or the external browser |
timeout around remoteCdpTimeoutMs (default 1500ms) | usually still CDP reachability, or a slow/unreachable remote endpoint |
Playwright page enumeration timed out after 3000ms | the remote CDP connected, but its persistent tab read stalled; the deadline is the larger of remoteCdpTimeoutMs and remoteCdpHandshakeTimeoutMs |
No Chrome tabs found for profile="user" | local Chrome MCP profile selected where no host-local tabs are available |
127.0.0.1 or [::1] answers on /json/version, and
does that listener belong to chrome.exe?curl http://WINDOWS_HOST_OR_IP:9222/json/version work?browser.profiles.<name>.cdpUrl use that exact
WSL2-reachable address?http://127.0.0.1:18789/ instead of a LAN IP?existing-session across WSL2 and Windows instead
of raw remote CDP?Verify the Windows Chrome endpoint locally first, verify the same endpoint from WSL2 second, and only then debug OpenClaw config or Control UI auth.