Skip to content

fix(cli): bind-probe 0.0.0.0 and loopback so macOS sees a busy port - #14923

Merged
diegosouzapw merged 1 commit into
diegosouzapw:release/v3.8.51from
peterbussch:fix/cli-loopback-bind-probe
Sep 29, 2026
Merged

diegosouzapw merged 1 commit into
diegosouzapw:release/v3.8.51from
peterbussch:fix/cli-loopback-bind-probe

Conversation

@peterbussch

Copy link
Copy Markdown
Contributor

Summary

On macOS, probePortFree() says a port is free while another OmniRoute holds it. The probe binds the wildcard address, and macOS allows that bind while another socket listens on the same port at 0.0.0.0 (the default serve host), 127.0.0.1 or ::1. Linux rejects it, so CI stays green, but two tests in tests/unit/cli-serve-port-in-use-preflight.test.mjs fail on a Mac.

To reproduce (macOS 27.0, Apple M4 Pro, Node 22.23.0, release/v3.8.51 at a58000c768):

$ node -e '
const net = require("node:net");
const s = net.createServer().listen(0, "0.0.0.0", async () => {
  const { probePortFree } = await import("./bin/cli/utils/pid.mjs");
  console.log("held on 0.0.0.0, probePortFree:", await probePortFree(s.address().port));
  s.close();
});'
held on 0.0.0.0, probePortFree: true

Cause: bin/cli/utils/pid.mjs:107-119 binds only the wildcard address. On macOS only a bind to the held address itself fails with EADDRINUSE.

Fix:

Related Issues

Note: #14812 fixes the lsof crash in #14800 and does not touch probePortFree. This is the remaining macOS part. The two branches merge cleanly in either order, and the combined test file passes 15/15.

Validation

  • Change type: CLI
  • Focused tests: node --import tsx/esm --test tests/unit/cli-serve-port-in-use-preflight.test.mjs: 13 passed on macOS. Before the fix: 10 passed, 3 failed (the new test and the two existing macOS failures).
  • npm run lint: I ran eslint and prettier --check on the changed files only. Both are clean.
  • Reconciled with the current active release base (release/v3.8.51 at a58000c768)
  • Production-code changes include a new or updated automated test in this PR

Tests Added Or Updated

  • tests/unit/cli-serve-port-in-use-preflight.test.mjs
    • probePortFree sees a held port when the wildcard bind succeeds (macOS) stubs deps.net with macOS bind semantics, so without the fix it fails on Linux CI too.
    • probePortFree treats a missing loopback address as free covers the fail-open path.

Coverage Notes

The two new tests reach every branch of the new loop in probePortFree without opening a socket.

Reviewer Notes

  • Linux behavior does not change. The first probe is still the wildcard bind, and it already fails there when the port is held.
  • A free port now costs four binds instead of one.
  • The probe now counts a listener on any of these addresses as busy, even if serve would bind a different one. findListeningPids() already works that way: lsof -ti :PORT ignores the address.

⚠️ base-red inherited: #14866

Copilot AI lite review requested due to automatic review settings September 27, 2026 05:29
macOS lets the probe's wildcard bind succeed while another socket holds the
port on 0.0.0.0, 127.0.0.1 or ::1, so probePortFree reported a held port as
free. Probe those addresses too.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@peterbussch
peterbussch force-pushed the fix/cli-loopback-bind-probe branch from ade57ea to 0fad5a1 Compare September 27, 2026 05:29
@diegosouzapw
diegosouzapw merged commit 1e6de43 into diegosouzapw:release/v3.8.51 Sep 29, 2026
11 of 16 checks passed
diegosouzapw pushed a commit that referenced this pull request Sep 29, 2026
serve no longer crashes when discovery returns no pids and the port is free on macOS; merged cleanly beside #14923's bind-probe. Boarded on the release/v3.8.51 tip (3a2678f) with the b3 release-drain batch (26 PRs): typecheck:core and the open-sse typecheck are clean, ESLint is clean on every changed file, migration numbering OK, and the focused tests of the whole board pass 611/611 (51 files). File-size ceiling growth is reconciled in the wave follow-up. Thank you @prabhtheone!
@peterbussch
peterbussch deleted the fix/cli-loopback-bind-probe branch October 3, 2026 20:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants