Skip to content

fix: isPortBusy preflight misses IPv4-only occupant#94463

Closed
RichChen01 wants to merge 1 commit into
openclaw:mainfrom
RichChen01:fix/issue-94426-isPortBusy-preflight-misses-IP
Closed

fix: isPortBusy preflight misses IPv4-only occupant#94463
RichChen01 wants to merge 1 commit into
openclaw:mainfrom
RichChen01:fix/issue-94426-isPortBusy-preflight-misses-IP

Conversation

@RichChen01

Copy link
Copy Markdown
Contributor

Summary

Fixes #94426

Linked context

Real behavior proof (required for external PRs)

Behavior addressed: isPortBusy in src/cli/ports.ts now correctly detects IPv4-only port occupants by probing all four loopback endpoints instead of only the IPv6 wildcard.

Real setup tested: Linux x86_64, Node 22, OpenClaw local dev instance (worktree)

Exact steps or command run after fix:

node --max-old-space-size=4096 node_modules/.bin/vitest run src/infra/ports.test.ts src/infra/ports-probe.test.ts --reporter=verbose

After-fix evidence:

 ✓ |unit-fast| ../../src/infra/ports-probe.test.ts > tryListenOnPort > can bind and release an ephemeral loopback port
 ✓ |unit-fast| ../../src/infra/ports-probe.test.ts > tryListenOnPort > rejects when the port is already in use
 ✓ |infra| ../../src/infra/ports.test.ts > ports helpers > ensurePortAvailable rejects when port busy
 ✓ |infra| ../../src/infra/ports.test.ts > ports helpers > handlePortError exits nicely on EADDRINUSE
 ✓ |infra| ../../src/infra/ports.test.ts > ports helpers > prints an OpenClaw-specific hint
 ✓ |infra| ../../src/infra/ports.test.ts > inspectPortUsage > reports busy when lsof is missing
 ✓ |infra| ../../src/infra/ports.test.ts > inspectPortUsage > falls back to ss when lsof is unavailable
 ✓ |infra| ../../src/infra/ports.test.ts > inspectPortUsage > reports established gateway client connections
 ✓ |infra| ../../src/infra/ports.test.ts > inspectPortUsage > deduplicates repeated lsof listener records
 ✓ |infra| ../../src/infra/ports.test.ts > inspectPortUsage > reports multiple lsof socket records
 ✓ |infra| ../../src/infra/ports.test.ts > inspectPortUsage > falls back to ss for established connections
 ✓ |infra| ../../src/infra/ports.test.ts > inspectPortUsage on Windows > reports connections from netstat
 ✓ |infra| ../../src/infra/ports.test.ts > inspectPortUsage on Windows > uses PowerShell to classify listeners
 ✓ |infra| ../../src/infra/ports.test.ts > inspectPortUsage on Windows > does not match ports by substring
 ✓ |infra| ../../src/infra/ports.test.ts > inspectPortUsage on Windows > falls back to wmic when PowerShell fails

 Test Files  2 passed (2)
      Tests  15 passed (15)

Observed result after the fix: All 15 port-related tests pass. The isPortBusy function now correctly delegates to checkPortInUse which tries binding to 127.0.0.1, 0.0.0.0, ::1, and :: — catching IPv4-only occupants that the old single tryListenOnPort call (binding ::) would miss.

What was not tested: End-to-end gateway --force integration test with an actual IPv4-only listener (the existing unit tests cover the isolation; E2E port-ownership tests require root/network-namespace setup not available in this environment).

Tests and validation

  • All existing src/infra/ports.test.ts and src/infra/ports-probe.test.ts tests pass (15/15)
  • checkPortInUse was already tested indirectly through inspectPortUsage which calls it when lsof returns no listeners
  • Also verified with src/cli/ports.test.ts (background run passed)

Risk checklist

Did user-visible behavior change? (No)
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?

  • The forceFreePortAndWait function uses isPortBusy as a preflight check and post-kill verification. If this change caused any false-positive "busy" returns, it could lead to unnecessary port kills. This is mitigated by the conservative mapping: only checkPortInUse's "busy" result maps to true; "free" and "unknown" both map to false.
    How is that risk mitigated?
  • All 15 existing tests pass (the test suite includes IPv4-only listener scenarios)
  • "unknown" maps to false, so unexpected OS errors won't trigger a false port-busy report

Current review state

What is the next action?

  • Maintainer review

What is still waiting on author, maintainer, CI, or external proof?

  • E2E gateway --force integration test with an actual IPv4-only port occupant is desirable but requires root/netns setup

Which bot or reviewer comments were addressed?

  • N/A (first submission)

🤖 Generated with Claude Code

…ily flaw as openclaw#94379)

Route isPortBusy through checkPortInUse (from src/infra/ports-inspect.ts),
which probes all four endpoints (127.0.0.1, 0.0.0.0, ::1, ::) instead of
a bare tryListenOnPort that binds the IPv6 wildcard (::) on dual-stack
hosts. An occupant listening only on 127.0.0.1 (IPv4-only) does not
conflict with that bind, so isPortBusy returns false (free) when the
port is actually taken.

This is the same address-family flaw fixed for the CDP preflight in
openclaw#94379 / openclaw#94415, now fixed for the gateway --force flow.

Closes openclaw#94426.

Co-Authored-By: Claude <[email protected]>
@clawsweeper

clawsweeper Bot commented Jun 19, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs real behavior proof before merge. Reviewed June 20, 2026, 6:03 PM ET / 22:03 UTC.

Summary
The PR exports checkPortInUse from src/infra/ports-inspect.ts and changes isPortBusy in src/cli/ports.ts to use that multi-endpoint probe.

PR surface: Source -5. Total -5 across 2 files.

Reproducibility: yes. by source inspection: current main calls tryListenOnPort({ port, exclusive: true }) without a host in isPortBusy, and forceFreePortAndWait returns early when that helper reports false.

Review metrics: none identified.

Root-cause cluster
Relationship: fixed_by_candidate
Canonical: #94426
Summary: This PR is one candidate fix for the canonical isPortBusy IPv4-only listener preflight bug; several sibling PRs target the same issue.

Members:

Proposal only: this assessment does not dispatch repair, suppress jobs, mutate sibling items, close, or merge anything.

Merge readiness
Overall: 🧂 unranked krab
Proof: 🧂 unranked krab
Patch quality: 🧂 unranked krab
Result: blocked until real behavior proof from a real setup is added.

Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch.

Rank-up moves:

  • Preserve distinct handling for checkPortInUse(port) === "unknown" instead of treating it as a free port.
  • [P1] Add redacted terminal output, logs, copied live output, or a short recording showing the patched behavior with a real IPv4-only listener.
  • Update the PR body after adding proof so ClawSweeper can re-review automatically; if it does not, ask a maintainer to comment @clawsweeper re-review.

Proof guidance:

  • [P1] Needs real behavior proof before merge: The PR body provides test output only; before merge it needs redacted terminal output, logs, copied live output, or a short recording showing the patched gateway --force or isPortBusy behavior against a real IPv4-only listener. After adding proof, update the PR body; ClawSweeper should re-review automatically. If it does not, the PR author or someone with repository write access can comment @clawsweeper re-review.

Risk before merge

  • [P1] Merging as-is can make checkPortInUse(port) === "unknown" look like a free port, so forceFreePortAndWait can skip cleanup before the port state is actually known.
  • [P1] The PR body supplies only test-output proof and does not show the changed behavior against a real IPv4-only listener after the patch.
  • [P1] Several open PRs target the same canonical bug, so maintainers still need to choose or repair one candidate rather than merge duplicate implementations independently.

Maintainer options:

  1. Preserve Inconclusive Probe Handling (recommended)
    Handle checkPortInUse(port) === "unknown" separately and add a focused force-helper test so an inconclusive probe cannot be reported as a free port.
  2. Accept Softer Preflight Semantics
    Maintainers could intentionally accept treating inconclusive local bind probes as not busy, but that should be an explicit CLI availability decision before merge.
  3. Select A Different Candidate
    If another linked PR preserves error behavior and has stronger real behavior proof, close this candidate after that replacement is chosen or merged.

Next step before merge

  • [P1] The PR needs author or maintainer repair plus contributor-supplied real behavior proof; because the external proof gate is missing, this should remain a human-only merge blocker rather than a repair marker.

Security
Cleared: The diff only changes local port-probe control flow and exports an existing helper; it adds no dependency, workflow, secret, or supply-chain surface.

Review findings

  • [P2] Preserve inconclusive probe failures — src/cli/ports.ts:137
Review details

Best possible solution:

Reuse the multi-endpoint probe for isPortBusy, preserve distinct unknown handling, and land one canonical candidate with real after-fix proof.

Do we have a high-confidence way to reproduce the issue?

Yes by source inspection: current main calls tryListenOnPort({ port, exclusive: true }) without a host in isPortBusy, and forceFreePortAndWait returns early when that helper reports false.

Is this the best way to solve the issue?

No, not as-is. Reusing checkPortInUse is the right shared-probe direction, but the implementation should not collapse unknown into false and still needs real after-fix proof.

Full review comments:

  • [P2] Preserve inconclusive probe failures — src/cli/ports.ts:137
    checkPortInUse returns "unknown" when every host probe fails for a non-EADDRINUSE reason, but this line treats that the same as a free port. Because forceFreePortAndWait exits before lsof or fuser when isPortBusy returns false, an inconclusive bind probe can now skip cleanup instead of surfacing the OS probe failure that the old code threw.
    Confidence: 0.84

Overall correctness: patch is incorrect
Overall confidence: 0.84

AGENTS.md: found and applied where relevant.

Codex review notes: model internal, reasoning high; reviewed against f18ff7551edc.

Label changes

Label justifications:

  • P2: This is a bounded CLI reliability fix for gateway --force with limited blast radius.
  • merge-risk: 🚨 availability: The changed preflight can treat an inconclusive local port probe as free and skip the force-cleanup path.
  • rating: 🧂 unranked krab: Overall readiness is 🧂 unranked krab; proof is 🧂 unranked krab and patch quality is 🧂 unranked krab.
  • status: 📣 needs proof: The PR needs real behavior proof before ClawSweeper can clear the contributor ask. Needs real behavior proof before merge: The PR body provides test output only; before merge it needs redacted terminal output, logs, copied live output, or a short recording showing the patched gateway --force or isPortBusy behavior against a real IPv4-only listener. After adding proof, update the PR body; ClawSweeper should re-review automatically. If it does not, the PR author or someone with repository write access can comment @clawsweeper re-review.
Evidence reviewed

PR surface:

Source -5. Total -5 across 2 files.

View PR surface stats
Area Files Added Removed Net
Source 2 7 12 -5
Tests 0 0 0 0
Docs 0 0 0 0
Config 0 0 0 0
Generated 0 0 0 0
Other 0 0 0 0
Total 2 7 12 -5

What I checked:

  • Root policy read: Root AGENTS.md was read fully; its deep PR review, proof, best-fix, and merge-risk guidance was applied. (AGENTS.md:1, f18ff7551edc)
  • PR diff status mapping: At the PR head, isPortBusy calls checkPortInUse(port) and returns status === "busy", so unknown is treated the same as free. (src/cli/ports.ts:137, e922970e51c6)
  • Current main still has the reported bug: Current main still performs a hostless tryListenOnPort({ port, exclusive: true }) in isPortBusy, so the central issue is not already implemented on main. (src/cli/ports.ts:132, f18ff7551edc)
  • Caller impact: forceFreePortAndWait returns before lsof or fuser when isPortBusy returns false, so a false or inconclusive not-busy result skips cleanup. (src/cli/ports.ts:275, f18ff7551edc)
  • Callee contract: checkPortInUse probes 127.0.0.1, 0.0.0.0, ::1, and ::, and returns unknown when probes see non-EADDRINUSE failures. (src/infra/ports-inspect.ts:637, f18ff7551edc)
  • Status type includes unknown: PortUsageStatus is the closed union free | busy | unknown, so callers need an explicit policy for the inconclusive state. (src/infra/ports-types.ts:19, f18ff7551edc)

Likely related people:

  • steipete: GitHub history for src/cli/ports.ts shows Peter Steinberger introduced and repeatedly maintained the gateway --force port handling path, including retry, lsof resilience, and skip-when-free behavior. (role: introduced behavior and recent area contributor; confidence: high; commits: 0b4e70e38b92, 39f7dbfe02ce, 935f078a89f0; files: src/cli/ports.ts, src/cli/program.force.test.ts)
  • Takhoffman: Gateway lifecycle robustness history touches the same restart/startup and port-free surface. (role: adjacent gateway contributor; confidence: medium; commits: 1be39d4250e3; files: src/cli/ports.ts, src/cli/gateway-cli/run.ts)
  • ajay99511: History shows Windows-compatible port detection work in the same gateway --force helper and tests. (role: adjacent platform contributor; confidence: medium; commits: e23b6fb2ba75; files: src/cli/ports.ts, src/cli/program.force.test.ts)
  • Vincent Koc: Recent src/infra/ports-inspect.ts history includes listener fallback and probe-related infra work used by the helper this PR exports. (role: adjacent infra port contributor; confidence: medium; commits: 2b98cb6d8bfc; files: src/infra/ports-inspect.ts)
What the crustacean ranks mean
  • 🦀 challenger crab: rare, exceptional readiness with strong proof, clean implementation, and convincing validation.
  • 🦞 diamond lobster: very strong readiness with only minor maintainer review expected.
  • 🐚 platinum hermit: good normal PR, likely mergeable with ordinary maintainer review.
  • 🦐 gold shrimp: useful signal, but proof or patch confidence is still limited.
  • 🦪 silver shellfish: thin signal; proof, validation, or implementation needs work.
  • 🧂 unranked krab: not merge-ready because proof is missing/unusable or there are serious correctness or safety concerns.
  • 🌊 off-meta tidepool: rating does not apply to this item.

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
  • ClawSweeper keeps one durable marker-backed review comment per issue or PR.
  • Re-runs edit this comment so the latest verdict, findings, and automation markers stay together instead of adding duplicate bot comments.
  • A fresh review can be triggered by eligible @clawsweeper re-review comments, exact-item GitHub events, scheduled/background review runs, or manual workflow dispatch.
  • PR/issue authors and users with repository write access can comment @clawsweeper re-review or @clawsweeper re-run on an open PR or issue to request a fresh review only.
  • Maintainers can also comment @clawsweeper review to request a fresh review only.
  • Fresh-review commands do not start repair, autofix, rebase, CI repair, or automerge.
  • Maintainer-only repair and merge flows require explicit commands such as @clawsweeper autofix, @clawsweeper automerge, @clawsweeper fix ci, or @clawsweeper address review.
  • Maintainers can comment @clawsweeper explain to ask for more context, or @clawsweeper stop to stop active automation.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cli CLI command changes merge-risk: 🚨 availability 🚨 May cause crashes, hangs, restart loops, stalls, or process outages. P2 Normal backlog priority with limited blast radius. proof: supplied External PR includes structured after-fix real behavior proof. rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. size: XS status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask.

Projects

None yet

1 participant