Skip to content

fix(paths): avoid Windows-excluded dynamic ports for daemon endpoints #1322

Description

@zackees

Context

fbuild 2.5.17 cannot start its daemon on this Windows host because the deterministic endpoint maps into a TCP exclusion range reserved by Windows/Hyper-V.

Observed with the current main binaries:

  • fbuild-paths::default_daemon_port() resolves the dev endpoint to 49785.
  • netsh interface ipv4 show excludedportrange protocol=tcp and the IPv6 equivalent both include 49690-49789 (followed by adjacent reserved blocks through 50459).
  • No process is listening on 49785.
  • A direct TcpListener bind to 0.0.0.0:49785 fails with WSAEACCES / native error 10013.
  • fbuild-daemon retries three times and exits; the CLI then repeats three 10-second readiness attempts and reports daemon did not become healthy after 3 spawn attempts.

Exact fbuild log:

bind to 0.0.0.0:49785 failed (An attempt was made to access a socket in a way forbidden by its access permissions. (os error 10013)); no listener present (stale state)

Control: setting FBUILD_DAEMON_PORT to a free port (62796 in this run) lets the same merged CLI/daemon start, complete clean sketch ... -e esp32dev, and stop normally.

Root cause

crates/fbuild-paths/src/lib.rs::port_from_endpoint_key intentionally maps every default endpoint into the IANA dynamic window 49152-65535. On Windows, that is also the default ephemeral allocation window and Hyper-V/HNS commonly reserves blocks inside it. A deterministic hash can therefore select a port the kernel rejects even when no listener exists. The daemon's bind retry treats the permanent access denial as transient stale TCP state, so retries cannot recover.

Proposal

On Windows, map endpoint keys into a deterministic user-space window below the default dynamic/exclusion range (for example 10000-49151), while retaining endpoint-key isolation and the explicit FBUILD_DAEMON_PORT override. Keep the existing dynamic mapping on platforms where it is not subject to Windows exclusion blocks.

Update the path contract/docs and add pure regression coverage proving Windows-derived defaults stay below 49152 while remaining deterministic and nonzero.

Acceptance criteria

Decisions made

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    Status
    No status

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions