fix: detect port conflicts on loopback addresses, not just 0.0.0.0#345
Conversation
There was a problem hiding this comment.
Code Review
This pull request updates the port availability check in src/supervisor/lifecycle.rs to verify multiple addresses (0.0.0.0, 127.0.0.1, and ::1), addressing false negatives on platforms like macOS/BSD where SO_REUSEADDR is the default. Feedback was provided regarding the IPv6 check, which may incorrectly report a port conflict on systems where IPv6 is disabled; handling specific IO errors for the ::1 address was suggested to ensure compatibility across different environments.
Greptile SummaryThis PR fixes a false-negative in Confidence Score: 5/5Safe to merge — the fix is correct, well-scoped, and the previously raised P1 concern is already addressed in the current code. No P0 or P1 findings remain. The prior thread's concern about No files require special attention. Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[check_ports_available called] --> B[For each candidate port]
B --> C{port == 0?}
C -- yes --> B
C -- no --> D[spawn_blocking]
D --> E[Try bind 0.0.0.0:port]
E -- Ok --> F[drop listener]
F --> G[Try bind 127.0.0.1:port]
G -- Ok --> H[drop listener]
H --> I[Try bind ::1:port]
I -- Ok --> J[drop listener → return true]
E -- AddrInUse --> K[return false]
G -- AddrInUse --> K
I -- AddrInUse --> K
E -- Other error --> G
G -- Other error --> I
I -- Other error --> J
K --> L[port_check = false]
J --> M[port_check = true]
L --> N[all_available = false, break]
M --> O{all ports available?}
O -- yes --> P[return resolved ports]
O -- no --> Q{auto_bump?}
Q -- yes --> R[increment bump_offset, retry]
Q -- no --> S[return PortError]
Reviews (2): Last reviewed commit: "fix: detect port conflicts on loopback a..." | Re-trigger Greptile |
On macOS/BSD, Rust's TcpListener::bind sets SO_REUSEADDR by default, which allows binding 0.0.0.0:port to succeed even when 127.0.0.1:port (or ::1:port) is already occupied — because the kernel considers them different addresses. This caused check_ports_available() to report a port as available when it was actually in use on loopback, leading to daemons silently falling back to a different port while pitchfork still reported the original one. Now check 0.0.0.0, 127.0.0.1, and ::1 for each candidate port so that a conflict on any loopback interface is detected reliably.
9057b24 to
9136a18
Compare
## 🤖 New release * `pitchfork-cli`: 2.5.0 -> 2.6.0 <details><summary><i><b>Changelog</b></i></summary><p> <blockquote> ## [2.6.0](v2.5.0...v2.6.0) - 2026-04-12 ### Added - *(proxy)* auto start when visiting the proxied URL ([#347](#347)) ### Fixed - some issues related to sudo supervisor ([#323](#323)) - *(port)* should fail when ready_port is in use ([#350](#350)) - *(deps)* update rcgen to 0.14 ([#349](#349)) - *(deps)* update reqwest to 0.13 ([#348](#348)) - detect port conflicts on loopback addresses, not just 0.0.0.0 ([#345](#345)) - narrow REAPED_STATUSES cfg to non-Linux unix only ([#346](#346)) - *(deps)* update rust crate ratatui to 0.30 ([#331](#331)) - *(deps)* update rust crate toml to v1 ([#344](#344)) - *(deps)* update rust crate strum to 0.28 ([#334](#334)) - *(deps)* update rust crate notify-debouncer-full to 0.7 ([#330](#330)) - *(deps)* update rust crate nix to 0.31 ([#329](#329)) - *(deps)* update rust crate listeners to 0.5 ([#328](#328)) - *(deps)* update rust crate sysinfo to 0.38 ([#335](#335)) - *(deps)* update rust crate cron to 0.16 ([#324](#324)) - *(deps)* update rust crate crossterm to 0.29 ([#325](#325)) ### Other - *(deps)* update rust crate rmcp to v1.4.0 ([#327](#327)) </blockquote> </p></details> --- This PR was generated with [release-plz](https://github.com/release-plz/release-plz/). <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > Low risk: this PR only bumps the crate version and updates release notes, with no runtime code changes. > > **Overview** > Prepares the `pitchfork-cli` **v2.6.0** release by bumping the package version from `2.5.0` to `2.6.0` in `Cargo.toml`/`Cargo.lock`. > > Updates `CHANGELOG.md` with the `2.6.0` release notes (proxy auto-start behavior, several fixes, and dependency updates). > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit faea6c5. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY -->
On macOS/BSD, Rust's TcpListener::bind sets SO_REUSEADDR by default, which allows binding 0.0.0.0:port to succeed even when 127.0.0.1:port (or ::1:port) is already occupied — because the kernel considers them different addresses. This caused check_ports_available() to report a port as available when it was actually in use on loopback, leading to daemons silently falling back to a different port while pitchfork still reported the original one.
Now check 0.0.0.0, 127.0.0.1, and ::1 for each candidate port so that a conflict on any loopback interface is detected reliably.