mirror of
https://github.com/NeuralNomadsAI/CodeNomad.git
synced 2026-08-10 08:53:28 +00:00
fix(server): retry Windows reserved auto ports (#632)
## Summary - retry an ephemeral port when an automatic listener receives Windows `EACCES` - keep explicitly configured ports strict - keep non-Windows `EACCES` behavior unchanged ## Context CodeNomad already treats 9898 and 9899 as preferred rather than mandatory ports. The fallback previously handled only `EADDRINUSE`, so a Windows port reservation could terminate Tauri startup before the operating system was asked for an available port. The specific WinNAT attribution in the issue remains unverified, but the observable startup failure is fixed regardless of which Windows reservation produced `EACCES`. ## Validation - focused listener retry test passes - server typecheck passes - server suite: 256 passed, 4 skipped, 1 pre-existing Windows-only `git-worktrees.test.ts` fixture failure - autonomous gatekeeper review will be published as a PR comment after opening Fixes #627
This commit is contained in:
parent
598353db5e
commit
a628fa8872
3 changed files with 27 additions and 9 deletions
|
|
@ -278,8 +278,8 @@ function resolveHost(input: string | undefined): string {
|
|||
return trimmed
|
||||
}
|
||||
|
||||
function programHasArg(argv: string[], flag: string): boolean {
|
||||
return argv.includes(flag)
|
||||
export function programHasArg(argv: string[], flag: string): boolean {
|
||||
return argv.some((argument) => argument === flag || argument.startsWith(`${flag}=`))
|
||||
}
|
||||
|
||||
async function main() {
|
||||
|
|
|
|||
18
packages/server/src/server/__tests__/listener-retry.test.ts
Normal file
18
packages/server/src/server/__tests__/listener-retry.test.ts
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
import assert from "node:assert/strict"
|
||||
import test from "node:test"
|
||||
|
||||
import { programHasArg } from "../../index"
|
||||
import { shouldRetryPreferredPort } from "../http-server"
|
||||
|
||||
test("automatic listeners retry Windows reserved ports without masking explicit failures", () => {
|
||||
assert.equal(shouldRetryPreferredPort({ code: "EADDRINUSE" }, true, "linux"), true)
|
||||
assert.equal(shouldRetryPreferredPort({ code: "EACCES" }, true, "win32"), true)
|
||||
assert.equal(shouldRetryPreferredPort({ code: "EACCES" }, true, "linux"), false)
|
||||
assert.equal(shouldRetryPreferredPort({ code: "EACCES" }, false, "win32"), false)
|
||||
})
|
||||
|
||||
test("explicit listener ports are detected in both supported CLI forms", () => {
|
||||
assert.equal(programHasArg(["--http-port", "9899"], "--http-port"), true)
|
||||
assert.equal(programHasArg(["--https-port=9898"], "--https-port"), true)
|
||||
assert.equal(programHasArg(["--http-porter=9899"], "--http-port"), false)
|
||||
})
|
||||
|
|
@ -82,6 +82,12 @@ interface HttpServerStartResult {
|
|||
displayHost: string
|
||||
}
|
||||
|
||||
export function shouldRetryPreferredPort(error: unknown, autoPortRequested: boolean, platform = process.platform): boolean {
|
||||
if (!autoPortRequested) return false
|
||||
const code = (error as NodeJS.ErrnoException | undefined)?.code
|
||||
return code === "EADDRINUSE" || (platform === "win32" && code === "EACCES")
|
||||
}
|
||||
|
||||
export function createHttpServer(deps: HttpServerDeps) {
|
||||
// Fastify's type-level RawServer inference gets noisy when toggling HTTP vs HTTPS.
|
||||
// We keep the runtime behavior correct and cast the instance to a generic FastifyInstance.
|
||||
|
|
@ -346,18 +352,12 @@ export function createHttpServer(deps: HttpServerDeps) {
|
|||
const autoPortRequested = deps.bindPort === 0
|
||||
const primaryPort = autoPortRequested ? deps.defaultPort : deps.bindPort
|
||||
|
||||
const shouldRetryWithEphemeral = (error: unknown) => {
|
||||
if (!autoPortRequested) return false
|
||||
const err = error as NodeJS.ErrnoException | undefined
|
||||
return Boolean(err && err.code === "EADDRINUSE")
|
||||
}
|
||||
|
||||
let listenResult
|
||||
|
||||
try {
|
||||
listenResult = await attemptListen(primaryPort)
|
||||
} catch (error) {
|
||||
if (!shouldRetryWithEphemeral(error)) {
|
||||
if (!shouldRetryPreferredPort(error, autoPortRequested)) {
|
||||
throw error
|
||||
}
|
||||
deps.logger.warn({ err: error, port: primaryPort }, "Preferred port unavailable, retrying on ephemeral port")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue