Bug type
Behavior bug (misleading error / incorrect pre-flight result)
Summary
ensurePortAvailable() (the pre-flight run before launching the managed Chrome in launchOpenClawChrome) uses a bare net.listen({ port }) with no host. On a dual-stack machine a host-less listener binds to the IPv6 wildcard ::, so an IPv4-only occupant of 127.0.0.1:<cdpPort> is not detected. The pre-flight reports the port as free, OpenClaw proceeds to spawn Chrome with --remote-debugging-port=<cdpPort>, and Chrome — which binds the CDP port on 127.0.0.1 — then fails with bind() failed: Address already in use (48). The CDP HTTP endpoint never comes up, and the surface error the user sees is a confusing Failed to start Chrome CDP ... HTTP 401, not a clear "port in use".
The address family the pre-flight probes (::) does not match the address family Chrome actually binds (127.0.0.1), so the guard that exists specifically to fail fast is silently bypassed.
Steps to reproduce
- Occupy the managed CDP port on IPv4 loopback only (any process bound to
127.0.0.1:18800; in my case a second OpenClaw-family gateway grabbed it first).
- Start the OpenClaw browser on the default profile (
openclaw browser start / any browser tool call), whose cdpPort defaults to 18800.
- Observe launch fails.
Minimal proof that the pre-flight probe disagrees with Chrome's bind (run while 127.0.0.1:18800 is occupied IPv4-only):
const net = require("node:net");
const tryListen = (opts) => new Promise((res, rej) => {
const s = net.createServer().once("error", rej).once("listening", () => s.close(() => res("free"))).listen(opts);
});
(async () => {
for (const opts of [{port:18800}, {port:18800,host:"127.0.0.1"}, {port:18800,host:"::"}, {port:18800,host:"0.0.0.0"}]) {
try { console.log(JSON.stringify(opts), "=>", await tryListen(opts)); }
catch (e) { console.log(JSON.stringify(opts), "=>", e.code); }
}
})();
Output:
{"port":18800} => free <- what ensurePortAvailable() does → false negative
{"port":18800,"host":"127.0.0.1"} => EADDRINUSE <- what Chrome actually hits
{"port":18800,"host":"::"} => free
{"port":18800,"host":"0.0.0.0"} => free
Expected behavior
ensurePortAvailable() should detect an IPv4-only occupant of the loopback CDP port and fail fast with the clear Port <n> is already in use. error (it already has nice owner-diagnostics via describePortOwner/handlePortError). The pre-flight should probe the same host family Chrome binds (127.0.0.1), or probe all loopback families.
Actual behavior
Pre-flight passes (false negative) → Chrome bind() fails → user sees Failed to start Chrome CDP on port <n> ... HTTP 401 + bind() failed: Address already in use. The 401 strongly misdirects toward auth/proxy debugging; the real cause is a plain port collision the guard was meant to catch.
Root cause
ensurePortAvailable(port) → tryListenOnPort({ port }) (no host) in src/infra/ports-probe.ts. A host-less net.listen binds the IPv6 wildcard on dual-stack hosts and does not observe an IPv4-only listener.
- Contrast: the sibling
checkPortInUse(port) in the same area does probe all four families ["127.0.0.1","0.0.0.0","::1","::"] and would have caught this. ensurePortAvailable (the one on the Chrome-launch path) does not reuse it.
- Chrome binds the debugging port on
127.0.0.1 (loopback), so the family mismatch is the whole bug.
Suggested fix
Make ensurePortAvailable consistent with Chrome's bind: either probe 127.0.0.1 explicitly, or delegate to the existing multi-family checkPortInUse/inspectPortUsage and throw PortInUseError when any loopback family is busy. Low blast radius — the multi-family probe already exists in the same module.
OpenClaw version
2026.6.8 (844f405)
Operating system
macOS 26.4 (arm64)
Node.js
v26.0.0 (bundled)
Chrome
149.0.7827.116
Install method
npm global
Impact and severity
- Severity: Medium. Not a crash, but the surfaced
HTTP 401 actively misdirects diagnosis (I initially chased auth/SSRF/proxy before finding the IPv4/IPv6 family mismatch).
- Frequency: 100% whenever the loopback CDP port has an IPv4-only occupant.
- The guard meant to produce a clear actionable error is silently bypassed.
Additional notes
The IPv4 occupant in my environment was a second gateway sharing the default 18800 range; I worked around it by pinning the profile's cdpPort to a free port. The dual-instance setup is incidental — the reportable defect is the pre-flight's address-family mismatch and the resulting misleading error, which reproduces for any IPv4-only occupant of the loopback CDP port.
Bug type
Behavior bug (misleading error / incorrect pre-flight result)
Summary
ensurePortAvailable()(the pre-flight run before launching the managed Chrome inlaunchOpenClawChrome) uses a barenet.listen({ port })with nohost. On a dual-stack machine a host-less listener binds to the IPv6 wildcard::, so an IPv4-only occupant of127.0.0.1:<cdpPort>is not detected. The pre-flight reports the port as free, OpenClaw proceeds to spawn Chrome with--remote-debugging-port=<cdpPort>, and Chrome — which binds the CDP port on127.0.0.1— then fails withbind() failed: Address already in use (48). The CDP HTTP endpoint never comes up, and the surface error the user sees is a confusingFailed to start Chrome CDP ... HTTP 401, not a clear "port in use".The address family the pre-flight probes (
::) does not match the address family Chrome actually binds (127.0.0.1), so the guard that exists specifically to fail fast is silently bypassed.Steps to reproduce
127.0.0.1:18800; in my case a second OpenClaw-family gateway grabbed it first).openclaw browser start/ any browser tool call), whosecdpPortdefaults to18800.Minimal proof that the pre-flight probe disagrees with Chrome's bind (run while
127.0.0.1:18800is occupied IPv4-only):Output:
Expected behavior
ensurePortAvailable()should detect an IPv4-only occupant of the loopback CDP port and fail fast with the clearPort <n> is already in use.error (it already has nice owner-diagnostics viadescribePortOwner/handlePortError). The pre-flight should probe the same host family Chrome binds (127.0.0.1), or probe all loopback families.Actual behavior
Pre-flight passes (false negative) → Chrome
bind()fails → user seesFailed to start Chrome CDP on port <n> ... HTTP 401+bind() failed: Address already in use. The 401 strongly misdirects toward auth/proxy debugging; the real cause is a plain port collision the guard was meant to catch.Root cause
ensurePortAvailable(port)→tryListenOnPort({ port })(nohost) insrc/infra/ports-probe.ts. A host-lessnet.listenbinds the IPv6 wildcard on dual-stack hosts and does not observe an IPv4-only listener.checkPortInUse(port)in the same area does probe all four families["127.0.0.1","0.0.0.0","::1","::"]and would have caught this.ensurePortAvailable(the one on the Chrome-launch path) does not reuse it.127.0.0.1(loopback), so the family mismatch is the whole bug.Suggested fix
Make
ensurePortAvailableconsistent with Chrome's bind: either probe127.0.0.1explicitly, or delegate to the existing multi-familycheckPortInUse/inspectPortUsageand throwPortInUseErrorwhen any loopback family is busy. Low blast radius — the multi-family probe already exists in the same module.OpenClaw version
2026.6.8 (844f405)
Operating system
macOS 26.4 (arm64)
Node.js
v26.0.0 (bundled)
Chrome
149.0.7827.116
Install method
npm global
Impact and severity
HTTP 401actively misdirects diagnosis (I initially chased auth/SSRF/proxy before finding the IPv4/IPv6 family mismatch).Additional notes
The IPv4 occupant in my environment was a second gateway sharing the default
18800range; I worked around it by pinning the profile'scdpPortto a free port. The dual-instance setup is incidental — the reportable defect is the pre-flight's address-family mismatch and the resulting misleading error, which reproduces for any IPv4-only occupant of the loopback CDP port.