From 05d1ee4eb90f40b7d8bb6976f28f2d22d3b65c95 Mon Sep 17 00:00:00 2001 From: Julio Quinteros Pro Date: Thu, 26 Feb 2026 15:40:32 -0300 Subject: [PATCH] fix(ui): resolve ERR_TOO_MANY_REDIRECTS on password reset with SERVER_ROOT_PATH The getUiConfig() function hardcoded `/litellm/.well-known/litellm-ui-config` which caused redirect loops when SERVER_ROOT_PATH was set (e.g., `/litellm`), especially in read-only Docker containers where the file-rewriting fallback silently fails. Now derives the config URL dynamically from the current page location, making it work correctly regardless of the configured root path. Co-Authored-By: Claude Opus 4.6 --- .../src/components/networking.test.ts | 16 ++++++++-------- .../src/components/networking.tsx | 13 ++++++++++--- 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/ui/litellm-dashboard/src/components/networking.test.ts b/ui/litellm-dashboard/src/components/networking.test.ts index c57dcb97eb9..887d75f3324 100644 --- a/ui/litellm-dashboard/src/components/networking.test.ts +++ b/ui/litellm-dashboard/src/components/networking.test.ts @@ -169,7 +169,7 @@ describe("UI config and public endpoints", () => { }; const mockFetch = setupMockFetch([ - { url: "/litellm/.well-known/litellm-ui-config", data: uiConfig }, + { url: "/.well-known/litellm-ui-config", data: uiConfig }, { url: "/public/providers/fields", data: [] }, ]); @@ -195,7 +195,7 @@ describe("UI config and public endpoints", () => { }; const mockFetch = setupMockFetch([ - { url: "/litellm/.well-known/litellm-ui-config", data: uiConfig }, + { url: "/.well-known/litellm-ui-config", data: uiConfig }, { url: "/public/model_hub/info", data: {} }, ]); @@ -218,7 +218,7 @@ describe("UI config and public endpoints", () => { }; const mockFetch = setupMockFetch([ - { url: "/litellm/.well-known/litellm-ui-config", data: uiConfig }, + { url: "/.well-known/litellm-ui-config", data: uiConfig }, { url: "/public/model_hub", data: [] }, ]); @@ -241,7 +241,7 @@ describe("UI config and public endpoints", () => { }; const mockFetch = setupMockFetch([ - { url: "/litellm/.well-known/litellm-ui-config", data: uiConfig }, + { url: "/.well-known/litellm-ui-config", data: uiConfig }, { url: "/public/agent_hub", data: [] }, ]); @@ -262,7 +262,7 @@ describe("UI config and public endpoints", () => { }; const mockFetch = setupMockFetch([ - { url: "/litellm/.well-known/litellm-ui-config", data: uiConfig }, + { url: "/.well-known/litellm-ui-config", data: uiConfig }, { url: "/public/mcp_hub", data: [] }, ]); @@ -283,7 +283,7 @@ describe("UI config and public endpoints", () => { }; const mockFetch = setupMockFetch([ - { url: "/litellm/.well-known/litellm-ui-config", data: uiConfig }, + { url: "/.well-known/litellm-ui-config", data: uiConfig }, { url: "/public/providers/fields", data: [] }, ]); @@ -305,14 +305,14 @@ describe("UI config and public endpoints", () => { proxy_base_url: "https://example.com", }; - const mockFetch = setupMockFetch([{ url: "/litellm/.well-known/litellm-ui-config", data: uiConfig }]); + const mockFetch = setupMockFetch([{ url: "/.well-known/litellm-ui-config", data: uiConfig }]); const result = await Networking.getUiConfig(); expect(mockFetch).toHaveBeenCalledOnce(); expect(result).toEqual(uiConfig); const configCall = mockFetch.mock.calls.find((call) => - (call[0] as string).includes("/litellm/.well-known/litellm-ui-config"), + (call[0] as string).includes("/.well-known/litellm-ui-config"), ); expect(configCall).toBeDefined(); }); diff --git a/ui/litellm-dashboard/src/components/networking.tsx b/ui/litellm-dashboard/src/components/networking.tsx index a20170a0fb0..5e9ee37fffd 100644 --- a/ui/litellm-dashboard/src/components/networking.tsx +++ b/ui/litellm-dashboard/src/components/networking.tsx @@ -362,9 +362,16 @@ export const makeModelGroupPublic = async (accessToken: string, modelGroups: str export const getUiConfig = async () => { console.log("Getting UI config"); /**Special route to get the proxy base url and server root path */ - const url = defaultProxyBaseUrl - ? `${defaultProxyBaseUrl}/litellm/.well-known/litellm-ui-config` - : `/litellm/.well-known/litellm-ui-config`; + let url: string; + if (defaultProxyBaseUrl) { + // Local development — use the explicit dev proxy URL + url = `${defaultProxyBaseUrl}/.well-known/litellm-ui-config`; + } else { + // Production — derive the base path from the current page URL + // The UI is served at {serverRootPath}/ui/*, so we go up from /ui/ to the root + const basePath = window.location.pathname.replace(/\/ui\/.*$/, ""); + url = `${basePath}/.well-known/litellm-ui-config`; + } const response = await fetch(url); const jsonData: LiteLLMWellKnownUiConfig = await response.json(); /**