Chrome binds remote-debugging port to IPv6 [::1] only — CDP connection fails (skill assumes IPv4)
Environment
- macOS (Darwin 25.x)
- Chrome 149, launched with
--remote-debugging-port=9222(dedicated user-data-dir) - web-access @
main—browser-discovery.mjs(138 lines) andcdp-proxy.mjsgetWebSocketUrlare IPv4-only
Problem
On recent macOS, Chrome's remote-debugging port is bound to IPv6 [::1]:9222 only — there is no IPv4 127.0.0.1 listener. The skill assumes IPv4 everywhere, so check-deps reports the browser as not connected:
$ lsof -nP -iTCP:9222 -sTCP:LISTEN
Google 46577 free ... TCP [::1]:9222 (LISTEN) # IPv6 only
$ curl http://127.0.0.1:9222/json/version # fails — nothing on IPv4
$ curl 'http://[::1]:9222/json/version' # works
{"Browser":"Chrome/149.0.7827.196","webSocketDebuggerUrl":"ws://[::1]:9222/devtools/browser/<uuid>",...}Three places all assume 127.0.0.1:
browser-discovery.mjs—checkPort(port)→net.createConnection(port, '127.0.0.1'); an IPv6-only port is invisible to it.browser-discovery.mjs—detectAll()readswsPathfrom theDevToolsActivePortfile, but that UUID goes stale after the previous Chrome instance exits (it changes every launch).cdp-proxy.mjs—getWebSocketUrl()buildsws://127.0.0.1:PORT..., which can't reach an IPv6-only listener.
Relation to #128
#128 (thanks @szxsq-728) fixes the stale-wsPath problem in fallback mode by fetching /json/version. But it (a) only covers the fallback branch, not the normal DevToolsActivePort discovery path, and (b) hardcodes http://127.0.0.1:PORT/json/version and rewrites the host to 127.0.0.1. On an IPv6-only bind, #128's fetch itself fails, and even if it got the wsPath, the rewritten ws://127.0.0.1 URL still can't connect. IPv6 is the blind spot — this issue complements #128.
Proposed fix (implemented & verified locally)
browser-discovery.mjs: addprobeDevtools(port)— HTTP-probe/json/versiontrying127.0.0.1first, then::1as fallback; return the actually-reachable host + the realwsPathparsed fromwebSocketDebuggerUrl. Use it in bothdetectAll()andfindFallbackPort()(now returns{port, host, wsPath}). IPv4-first ⇒ zero behavior change for IPv4 users.cdp-proxy.mjs:getWebSocketUrl(host, port, wsPath)buildsws://${host}and brackets IPv6 (ws://[::1]:PORT/...); threadchromeHostthroughconnect().cdp-proxy.mjsenablePortGuard: addhttp://[::1]:PORT/*to the port-probe interception patterns.check-deps.mjs: adapt the empty-branch to the newfindFallbackPort()return shape.
Verification (macOS, Chrome bound to [::1]:9222)
$ node check-deps.mjs --browser chrome
node: ok (v22.22.3)
browser: ok (Chrome, port 9222)
proxy: ready (Chrome)
$ curl -s http://127.0.0.1:3456/targets | jq 'length'
2getWebSocketUrl output for IPv4 inputs is byte-identical to before (no regression).
Happy to turn this into a PR if the approach looks right. cc @szxsq-728 — it overlaps with #128 on the stale-wsPath part but adds the IPv4/IPv6 host handling, which I believe is the more fundamental issue here.
Source: eze-is/web-access