fix(ports): route isPortBusy through checkPortInUse to catch IPv4-only occupants#94949
Conversation
|
Codex review: needs maintainer review before merge. Reviewed June 23, 2026, 5:31 PM ET / 21:31 UTC. Summary PR surface: Source -4, Tests -3. Total -7 across 2 files. Reproducibility: yes. source-reproducible: current main still performs a hostless Review metrics: none identified. 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. Rank-up moves:
Risk before merge
Maintainer options:
Next step before merge
Security Review detailsBest possible solution: Land one canonical focused fix that uses the shared multi-endpoint probe for Do we have a high-confidence way to reproduce the issue? Yes, source-reproducible: current main still performs a hostless Is this the best way to solve the issue? Yes. Using the existing AGENTS.md: found and applied where relevant. Codex review notes: model internal, reasoning high; reviewed against 654544b6b7c4. Label changesLabel justifications:
Evidence reviewedPR surface: Source -4, Tests -3. Total -7 across 2 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
|
ad466f2 to
e55d302
Compare
|
@clawsweeper re-review |
|
🦞🧹 I asked ClawSweeper to review this item again. |
e1fe413 to
9072d1e
Compare
Dependency graph guard clearedThis PR no longer has blocked dependency graph changes. A future dependency graph change requires a fresh
|
Per ClawSweeper review: checkPortInUse returns 'unknown' when every host probe fails for a non-EADDRINUSE reason. Treating unknown as 'not busy' could cause forceFreePortAndWait to exit before lsof/fuser inspects the port. Conservative fix: only 'free' means not busy; everything else (busy or unknown) triggers further inspection.
|
Merged via squash.
|
Summary
Fix
isPortBusypreflight to detect IPv4-only port occupants by routing through the existing multi-endpointcheckPortInUseprobe instead of a single hostlesstryListenOnPortbind. Also treat inconclusive probe results ("unknown") as busy soforceFreePortAndWaitdoes not exit early before lsof/fuser can inspect.Before:
isPortBusycalledtryListenOnPort({ port, exclusive: true })without specifying a host. On dual-stack hosts this binds the IPv6 wildcard (::), which does not conflict with an occupant listening only on127.0.0.1(IPv4-only). Result:isPortBusyreturnsfalse(port appears free) when it is actually occupied.After:
isPortBusyroutes throughcheckPortInUsewhich probes all four endpoints —127.0.0.1,0.0.0.0,::1,::— and maps"busy"or"unknown"totrue. Only"free"meansfalse. This is the same address-family invariant fixed for the CDP preflight in #94415.ClawSweeper-review follow-up: ClawSweeper flagged that
checkPortInUsereturns"unknown"when every host probe fails for a non-EADDRINUSEreason (P1). The original=== "busy"check collapsed"unknown"into a false not-busy result, potentially causingforceFreePortAndWaitto exit before lsof/fuser inspects the port. Fixed by checking!== "free"— only a confirmed free port skips further inspection.Closes #94426.
Linked context
isPortBusyIPv4-only listener false-negativesrc/infra/ports.ts)checkPortInUseas the canonical approach and flagged"unknown"status handling (both addressed)Real behavior proof (required for external PRs)
Behavior addressed:
isPortBusynow detects IPv4-only port occupants on dual-stack Linux hosts where the old hostlesstryListenOnPortbind would miss them. Additionally, inconclusive probe results ("unknown") are conservatively treated as busy, ensuringforceFreePortAndWaitcontinues to lsof/fuser inspection rather than exiting early.Real setup tested:
Exact steps or command run after fix:
After-fix evidence:
Before this fix, the old hostless
tryListenOnPortwould bind the IPv6 wildcard::which does not conflict with an IPv4-only listener on127.0.0.1, returningfalseand causingforceFreePortAndWaitto skip cleanup.Observed result after the fix:
isPortBusycorrectly returnstruewhen an IPv4-only listener occupies the port. ThecheckPortInUseprobe hits127.0.0.1first in its host list, triggersEADDRINUSE, and returns"busy". The!== "free"gate also conservatively treats"unknown"as busy, preserving the safety property thatforceFreePortAndWaitonly exits early when the port is confirmed free across all four address families.What was not tested: Live dual-stack reproduction where IPv6 wildcard and IPv4-only listeners genuinely do not conflict (depends on kernel
net.ipv6.bindv6onlysysctl). Cross-platform behavior on macOS and Windows. Fullgateway --forceend-to-end flow with a real IPv4-only occupant.Proof limitations or environment constraints: This Linux 4.19 kernel has
net.ipv6.bindv6only=0(default), which means::binds both IPv4 and IPv6, preventing a clean before/after demonstration on this host. The fix's correctness relies on the code-level invariant: probing127.0.0.1,0.0.0.0,::1, and::covers all address families regardless of kernel sysctl settings. The unit test suite covers the code paths; thetsxlive probe above confirmscheckPortInUseintegration on real hardware.Tests and validation
pnpm test src/cli/program.force.test.ts— 17/17 pass (tests force-free helpers including isPortBusy via mock)npx tsxreal-process test above confirmscheckPortInUsedetects a real IPv4-onlynet.createServerlistenermockResolvedValueOnce→mockResolvedValueto match the 4-host probe pattern (eachcheckPortInUsecall probes up to 4 hosts; final poll needs all hosts free)Risk checklist
Did user-visible behavior change? (
No)isPortBusyis a private helper; public APIforceFreePortAndWaitcontract unchanged. Gateway operators see no behavioral difference except that--forcenow reliably reclaims IPv4-only occupied ports where it previously failed silently.Did config, environment, or migration behavior change? (
No)Did security, auth, secrets, network, or tool execution behavior change? (
No)What is the highest-risk area?
"unknown"→ busy treatment means inconclusive OS probe failures (non-EADDRINUSEerrors from all 4 hosts) now route to lsof/fuser instead of exiting early. On systems where lsof/fuser are unavailable and probes fail for an exotic reason,forceFreePortAndWaitwould throw from lsof/fuser rather than silently reporting the port as free.How is that risk mitigated?
"unknown"treated as free) was arguably worse: it would skip cleanup entirely when port state was genuinely unknown. The new behavior surfaces the problem through existing lsof/fuser error paths, which already have user-facing error messages for missing tools. An operator who hits this edge case will see a clear "lsof not found" or "fuser not found" error rather than a silent port conflict.Current review state
What is the next action?
checkPortInUse✓, preserve"unknown"status ✓)What is still waiting on author, maintainer, CI, or external proof?
Which bot or reviewer comments were addressed?
=== "busy"→!== "free"with inline comment explaining why"unknown"must not exit earlynpx tsxprobe against real IPv4-onlynet.createServerlistenercheckPortInUse— implemented as the canonical approach