Skip to content

Commit 8c6573d

Browse files
calliclesclaude
andcommitted
fix(cli): prefer IPv4 when binding webserver to "localhost"
`tokio::net::lookup_host("localhost:<port>")` returns both 127.0.0.1 and ::1 on Debian-family systems. The previous `.next().unwrap()` took whichever came first — typically [::1] — which silently broke clients that resolve `localhost` to 127.0.0.1 first (notably Node's `fetch`, used by DEC Bench's assertion runner). Symptom: `moose dev` listens on [::1]:4000, but `fetch("http://localhost:4000")` hits 127.0.0.1 → ECONNREFUSED → assertion falls through to whatever else binds 8080 (Temporal UI's SvelteKit catch-all SPA, returning text/html for any path) → robust assertions fail with "valid JSON: false". Fix: collect all resolved addresses and prefer the first IPv4. Falls back to the first match if no IPv4 is present (so explicit IPv6-only hosts still work). This also matches what every other moose-spawned listener (devkafka, devredis, the moose-runner child, spawned ClickHouse and Temporal subprocesses) already binds to. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent cb63c58 commit 8c6573d

1 file changed

Lines changed: 18 additions & 4 deletions

File tree

apps/framework-cli/src/cli/local_webserver.rs

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2566,10 +2566,24 @@ impl Webserver {
25662566
}
25672567

25682568
async fn get_socket(&self, port: u16) -> SocketAddr {
2569-
tokio::net::lookup_host(format!("{}:{}", self.host, port))
2570-
.await
2571-
.unwrap()
2572-
.next()
2569+
// Prefer IPv4 when `host` resolves to both. On Debian-family systems
2570+
// /etc/hosts lists `::1 localhost` first, so a naive `.next()` ends up
2571+
// binding only to `[::1]` — which silently breaks clients that resolve
2572+
// `localhost` to 127.0.0.1 first (notably Node's `fetch`, which the
2573+
// DEC Bench asserter uses). Sticking to IPv4 also matches what every
2574+
// other moose-spawned listener (devkafka, devredis, the moose-runner
2575+
// child, the spawned ClickHouse and Temporal subprocesses) does, so
2576+
// bind ABI is consistent across the dev stack.
2577+
let candidates: Vec<SocketAddr> =
2578+
tokio::net::lookup_host(format!("{}:{}", self.host, port))
2579+
.await
2580+
.unwrap()
2581+
.collect();
2582+
candidates
2583+
.iter()
2584+
.find(|a| a.is_ipv4())
2585+
.copied()
2586+
.or_else(|| candidates.first().copied())
25732587
.unwrap()
25742588
}
25752589
pub async fn socket(&self) -> SocketAddr {

0 commit comments

Comments
 (0)