From 3f3a24b4189a6c143d691c0747af3f284ff8928f Mon Sep 17 00:00:00 2001 From: Eva H <63033505+hoyyeva@users.noreply.github.com> Date: Thu, 19 Mar 2026 12:57:57 -0400 Subject: [PATCH] app: fix desktop app stuck loading when OLLAMA_HOST is an unspecified bind address (#14885) --- app/ui/ui.go | 2 +- envconfig/config.go | 23 +++++++++++++++++++++++ envconfig/config_test.go | 31 +++++++++++++++++++++++++++++++ 3 files changed, 55 insertions(+), 1 deletion(-) diff --git a/app/ui/ui.go b/app/ui/ui.go index f720fe05a..5920192ba 100644 --- a/app/ui/ui.go +++ b/app/ui/ui.go @@ -155,7 +155,7 @@ func (s *Server) ollamaProxy() http.Handler { return } - target := envconfig.Host() + target := envconfig.ConnectableHost() s.log().Info("configuring ollama proxy", "target", target.String()) newProxy := httputil.NewSingleHostReverseProxy(target) diff --git a/envconfig/config.go b/envconfig/config.go index ec36d451b..7f524723a 100644 --- a/envconfig/config.go +++ b/envconfig/config.go @@ -59,6 +59,29 @@ func Host() *url.URL { } } +// ConnectableHost returns Host() with unspecified bind addresses (0.0.0.0, ::) +// replaced by the corresponding loopback address (127.0.0.1, ::1). +// Unspecified addresses are valid for binding a server socket but not for +// connecting as a client, which fails on Windows. +func ConnectableHost() *url.URL { + u := Host() + host, port, err := net.SplitHostPort(u.Host) + if err != nil { + return u + } + + if ip := net.ParseIP(host); ip != nil && ip.IsUnspecified() { + if ip.To4() != nil { + host = "127.0.0.1" + } else { + host = "::1" + } + u.Host = net.JoinHostPort(host, port) + } + + return u +} + // AllowedOrigins returns a list of allowed origins. AllowedOrigins can be configured via the OLLAMA_ORIGINS environment variable. func AllowedOrigins() (origins []string) { if s := Var("OLLAMA_ORIGINS"); s != "" { diff --git a/envconfig/config_test.go b/envconfig/config_test.go index 9242e66f5..1a9d77f07 100644 --- a/envconfig/config_test.go +++ b/envconfig/config_test.go @@ -52,6 +52,37 @@ func TestHost(t *testing.T) { } } +func TestConnectableHost(t *testing.T) { + cases := map[string]struct { + value string + expect string + }{ + "empty": {"", "http://127.0.0.1:11434"}, + "localhost": {"127.0.0.1", "http://127.0.0.1:11434"}, + "localhost and port": {"127.0.0.1:1234", "http://127.0.0.1:1234"}, + "ipv4 unspecified": {"0.0.0.0", "http://127.0.0.1:11434"}, + "ipv4 unspecified + port": {"0.0.0.0:1234", "http://127.0.0.1:1234"}, + "ipv6 unspecified": {"[::]", "http://[::1]:11434"}, + "ipv6 unspecified + port": {"[::]:1234", "http://[::1]:1234"}, + "ipv6 localhost": {"[::1]", "http://[::1]:11434"}, + "ipv6 localhost + port": {"[::1]:1234", "http://[::1]:1234"}, + "specific address": {"192.168.1.5", "http://192.168.1.5:11434"}, + "specific address + port": {"192.168.1.5:8080", "http://192.168.1.5:8080"}, + "hostname": {"example.com", "http://example.com:11434"}, + "hostname and port": {"example.com:1234", "http://example.com:1234"}, + "https unspecified + port": {"https://0.0.0.0:4321", "https://127.0.0.1:4321"}, + } + + for name, tt := range cases { + t.Run(name, func(t *testing.T) { + t.Setenv("OLLAMA_HOST", tt.value) + if host := ConnectableHost(); host.String() != tt.expect { + t.Errorf("%s: expected %s, got %s", name, tt.expect, host.String()) + } + }) + } +} + func TestOrigins(t *testing.T) { cases := []struct { value string