Skip to content

fix(cli): resolve serve preflight busy pids to an array when discovery returns null - #15021

Closed
krzemienski wants to merge 1 commit into
diegosouzapw:release/v3.8.51from
krzemienski:fix/serve-preflight-null-busy-pids
Closed

krzemienski wants to merge 1 commit into
diegosouzapw:release/v3.8.51from
krzemienski:fix/serve-preflight-null-busy-pids

Conversation

@krzemienski

Copy link
Copy Markdown

Problem

After #14538, omniroute serve crashes on every start where the target port is free and pid discovery can't answer:

✖ Cannot read properties of null (reading 'length')
    at runServe (bin/cli/commands/serve.mjs:261:16)

findListeningPids() returns null when discovery throws, and the bind-probe fallback only reassigns busyPids when the probe finds the port held. On a free port busyPids stays null, and busyPids.length throws.

On macOS this is the normal path, not an edge case: lsof -ti :PORT exits 1 when nothing is listening, execFile rejects on that, and findListeningPids() maps it to null. A launchd KeepAlive install that upgrades to current release/v3.8.51 crash-loops. Reproduced on macOS arm64, Node 22.22.3.

Fix

Tests

4 cases appended to tests/unit/cli-serve-port-in-use-preflight.test.mjs: null discovery with a free port gives [], null discovery with a held port gives [null], discovered pids skip the probe, and a real free port with the real lsof gives []. All pass.

Heads-up, unrelated to this change: on macOS, tests 7 and 9 in that file (probePortFree is false while a socket holds the port… and the #14518 end-to-end test) also fail on unmodified release/v3.8.51. probePortFree() binds the dual-stack wildcard, which succeeds while the test's holder is bound to 127.0.0.1 only.

Verified live

Built release/v3.8.51 @ d4bf564, installed it as the global package under launchd, and confirmed the crash loop. With this patch, serve starts cleanly, and /api/health plus chat completions answer 200 locally and through a Cloudflare tunnel.

…y returns null

diegosouzapw#14538 made findListeningPids() return null when discovery cannot answer and
bind-probed the port instead, but only reassigned busyPids when the probe
found the port held. On a free port busyPids stayed null and the following
busyPids.length threw, so `omniroute serve` crashed on every start with
"Cannot read properties of null (reading 'length')".

On macOS this hits every normal start: `lsof -ti :PORT` exits 1 when nothing
listens, which execFile surfaces as an error and findListeningPids() maps to
null. launchd/KeepAlive installs crash-loop after upgrading.

Extract the decision into resolveBusyPortPids() in bin/cli/utils/pid.mjs,
which always returns an array: discovered pids, [] when the bind probe finds
the port free, or [null] when the probe proves it held without a known owner.
serve.mjs uses it in place of the inline branches.
@diegosouzapw

Copy link
Copy Markdown
Owner

Thanks, @krzemienski. The null-discovery crash in the serve preflight was a real issue.

It is already fixed on release/v3.8.51 by #14812: resolveServeBusyPids() now handles findListeningPids() returning null by falling back to a bind probe, and returns [] when the port is free. Since that covers this PR, I'm closing it. Thanks for the report and the fix.

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.

2 participants