fix(ports): probe all address families in ensurePortAvailable#94415
fix(ports): probe all address families in ensurePortAvailable#94415wangwllu wants to merge 3 commits into
Conversation
A bare listen binds the IPv6 wildcard on dual-stack hosts and misses an IPv4-only occupant, so the preflight reports a busy port as free. Chrome then binds 127.0.0.1 via --remote-debugging-port, collides, and the CDP HTTP endpoint fails its handshake with a misleading 401. Route the preflight through checkPortInUse, which probes 127.0.0.1, 0.0.0.0, ::1 and :: so an occupant on any family is caught early with a proper PortInUseError. Heavy lsof/ss diagnostics stay in handlePortError, on failure only. Fixes openclaw#94379 Co-Authored-By: Claude <[email protected]>
|
@clawsweeper re-review |
|
🦞🧹 I asked ClawSweeper to review this item again. Re-review progress:
|
PR #94415 ReviewNon-issues
Minor suggestions (non-blocking)
|
Lock in that ensurePortAvailable resolves for a genuinely free port, and rename the IPv4-only case to reference the issue it guards. Co-Authored-By: Claude <[email protected]>
|
Thanks for the review @NianJiuZst — addressed all three:
15/15 unit tests pass; oxlint clean. |
|
Codex review: needs maintainer review before merge. Reviewed June 18, 2026, 8:55 AM ET / 12:55 UTC. Summary PR surface: Source +4, Tests +41. Total +45 across 3 files. Reproducibility: yes. by source and supplied proof: current main uses a hostless Review metrics: 1 noteworthy metric.
Root-cause cluster Members:
Proposal only: this assessment does not dispatch repair, suppress jobs, mutate sibling items, close, or merge anything. Merge readiness Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch. Risk before merge
Maintainer options:
Next step before merge
Security Review detailsBest possible solution: Land this narrow multi-family CDP preflight fix, or an equivalent canonical PR, after maintainer acceptance of the SDK-visible fail-fast behavior; keep Do we have a high-confidence way to reproduce the issue? Yes by source and supplied proof: current main uses a hostless Is this the best way to solve the issue? Yes: reusing the existing multi-family AGENTS.md: found and applied where relevant. Codex review notes: model internal, reasoning high; reviewed against 00a06eab4485. Label changesLabel changes:
Label justifications:
Evidence reviewedPR surface: Source +4, Tests +41. Total +45 across 3 files. View PR surface stats
What I checked:
Likely related people:
What the crustacean ranks mean
Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics. How this review workflow works
|
|
I think the fix would involve adjusting test. Would that work for your use case? |
ensurePortAvailable routed through checkPortInUse only threw on "busy", silently treating "unknown" (a bind probe that failed with something other than EADDRINUSE, e.g. EACCES/EINVAL) as available. The original bare-listen path rethrew such errors; restore that fail-fast and add an unknown-path regression guard.
Summary
ensurePortAvailabledid its preflight with a baretryListenOnPort, which binds the IPv6 wildcard (::) on dual-stack hosts. An occupant listening only on127.0.0.1(IPv4-only) is invisible to that probe, so the preflight reports a busy port as free.Downstream, Chrome takes that "free" port via
--remote-debugging-portand binds127.0.0.1for real, collides with the existing IPv4-only occupant, and the CDP HTTP endpoint never comes up — surfacing as a misleading CDP 401 handshake failure (the symptom behind #94379).The fix routes the preflight through
checkPortInUse, which probes all four address endpoints —127.0.0.1,0.0.0.0,::1,::— so an occupant on any family is caught early with a properPortInUseError. The heavylsof/ssdiagnostics ininspectPortUsagestay where they were: inhandlePortError, on failure only. Net cost is one new export and four lightweight socket probes at startup.Root cause
listen()on the IPv6 wildcard::127.0.0.1does not conflict with a::bind on a dual-stack host127.0.0.1→ EADDRINUSE at the socket layer → CDP endpoint fails → misleading 401Real behavior proof
::and never notices an occupant bound only to127.0.0.1, so it green-lights a port that is actually taken. Chrome then binds127.0.0.1via--remote-debugging-port, collides, and the CDP endpoint fails its handshake with a misleading 401 ([Bug]: ensurePortAvailable() pre-flight misses IPv4-only loopback occupant → Chrome CDP bind fails with misleading "HTTP 401" #94379).127.0.0.1, then exercising the old probe (listen ::) and the new probe (listen 127.0.0.1, one of the address familiescheckPortInUsenow covers) against that same port.node /tmp/repro-94379.mjs— bind an IPv4-only occupant on127.0.0.1:<p>, then attempt the old-style::wildcard bind and the new-style127.0.0.1bind on<p>, printing what each returns.127.0.0.1(among all families) and getsEADDRINUSEagainst the IPv4-only occupant → classifies the port as busy → throwsPortInUseError, so the taken port is no longer let through. The old logic bound::successfully on the same scenario and wrongly reported the port free.--remote-debugging-portlaunch (the root cause lives at the socket address-family layer and is reproduced/fixed there). The siblingisPortBusyinsrc/cli/ports.tsshares the same flaw and is left for a separate follow-up.Scope note
A sibling
isPortBusy(src/cli/ports.ts, thegateway --forcekill-port guard) shares the same bare-listen flaw but on a different surface (force-kill flow, not CDP preflight) with lower harm. Left untouched to keep this PR scoped to #94379; happy to follow up separately.Fixes #94379