feat(fetch): pin *.localhost connects to loopback (IPv4 by default) - #119
Merged
Conversation
createFetch now points the *.localhost node:http path straight at the
loopback interface instead of resolving bare localhost, taking DNS out of
the loopback hop. localhost commonly resolves to IPv6 ::1 first while many
local dev ingresses (kind, Docker port publishing) listen on IPv4 only, so
the old DNS-driven connect hit ::1 and failed without Happy-Eyeballs
fallback. The original Host header is still preserved for subdomain routing.
Adds createFetch({ loopback }) — '127.0.0.1' (default) | '::1' | false
(pre-1.2 DNS behavior).
Contributor
🤖 Devin AI EngineerI'll be helping with this pull request! Here's what you should know: ✅ I will automatically:
Note: I can only respond to comments from users who have write access to this repository. ⚙️ Control Options:
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
@constructive-io/fetch'screateFetch()node:http path rewrote*.localhost→ barelocalhostand then relied on system DNS to reach the loopback.localhostcommonly resolves to IPv6::1first, but many local dev ingresses (kind, Docker's port publishing) listen on IPv4 only — so the DNS-driven connect hit::1and failed withECONNREFUSED/socket hang upon setups without Node's Happy-Eyeballs fallback. This was the root cause of a false-negative health gate inconstructive-db(fun up --k8sstep 15 failing healthy clusters).Fix: point the connect directly at the loopback interface (IPv4
127.0.0.1by default), taking DNS out of the loopback hop entirely. The originalHostheader is still sent, so Traefik/subdomain routing is unaffected.New opt-in knob (default preserves the common case,
falserestores pre-1.2 behavior):Notes:
loopbackbuilds a fresh instance.optionsfor signature parity (browsers resolve*.localhostnatively).*.localhostURLs still delegate toglobalThis.fetchunchanged.Test plan
__tests__/localhost-fetch.test.tsto bind real servers on explicit families:createFetch()reaches an IPv4-only (127.0.0.1) server and preserves theHostheader (this is the regression the fix targets);loopback: falsereaches a DNS-resolvedlocalhostserver;loopback: '::1'reaches an IPv6-only server (guarded — skips when IPv6 loopback is unavailable);pnpm build+pnpm test(21 passing) +eslint packages/fetchclean.Follow-up
Once released,
constructive-db'sfun-k8s/health.tsprobe (PR #2846) can drop its bespokehttp.request({ family: 4 })plumbing and just usecreateFetch().Link to Devin session: https://app.devin.ai/sessions/6f4d8dd9a5834871a379b7e598ccde8c
Requested by: @pyramation