diff --git a/tests/test_litellm/proxy/management_endpoints/test_team_endpoints.py b/tests/test_litellm/proxy/management_endpoints/test_team_endpoints.py
index c28e398e451..4c99f154d7b 100644
--- a/tests/test_litellm/proxy/management_endpoints/test_team_endpoints.py
+++ b/tests/test_litellm/proxy/management_endpoints/test_team_endpoints.py
@@ -14224,10 +14224,6 @@ async def test_team_info_returns_parent_organization_models(organization, expect
user_api_key_dict=UserAPIKeyAuth(user_role=LitellmUserRoles.PROXY_ADMIN),
)
- include = mock_prisma.db.litellm_teamtable.find_unique.await_args.kwargs["include"]
- assert include["litellm_organization_table"] is True
-
team_info = response["team_info"]
assert team_info.organization_models == expected_models
- # the org row itself carries budgets and spend; only its model list may ride along
assert "litellm_organization_table" not in team_info.model_dump()
diff --git a/ui/litellm-dashboard/src/components/ModelSelect/ModelSelect.test.tsx b/ui/litellm-dashboard/src/components/ModelSelect/ModelSelect.test.tsx
index 690d41586d0..3ea42f5adc0 100644
--- a/ui/litellm-dashboard/src/components/ModelSelect/ModelSelect.test.tsx
+++ b/ui/litellm-dashboard/src/components/ModelSelect/ModelSelect.test.tsx
@@ -473,6 +473,50 @@ describe("ModelSelect", () => {
}
});
+ it("should stay in the loading state while a list-seeded team is still fetching its org ceiling", () => {
+ mockUseOrganization.mockReturnValue({ data: undefined, isLoading: false } as any);
+ mockUseTeam.mockReturnValue({
+ data: { team_id: "team-1", models: [] },
+ isLoading: false,
+ isFetching: true,
+ } as any);
+
+ renderWithProviders(
+ ,
+ );
+
+ expect(screen.queryAllByRole("combobox")).toHaveLength(0);
+ });
+
+ it("should not hold the loading state on a background refetch once the org ceiling is known", async () => {
+ const user = userEvent.setup();
+ mockUseOrganization.mockReturnValue({ data: undefined, isLoading: false } as any);
+ mockUseTeam.mockReturnValue({
+ data: { team_id: "team-1", organization_models: ["all-proxy-models"] },
+ isLoading: false,
+ isFetching: true,
+ } as any);
+
+ renderWithProviders(
+ ,
+ );
+
+ await openModelList(user);
+ expectOffered("All Proxy Models");
+ });
+
it("should keep hiding All Proxy Models when neither the team nor the org reports a ceiling", async () => {
const user = userEvent.setup();
mockUseOrganization.mockReturnValue({ data: undefined, isLoading: false } as any);
diff --git a/ui/litellm-dashboard/src/components/ModelSelect/ModelSelect.tsx b/ui/litellm-dashboard/src/components/ModelSelect/ModelSelect.tsx
index 76d95df6962..ca449be81e3 100644
--- a/ui/litellm-dashboard/src/components/ModelSelect/ModelSelect.tsx
+++ b/ui/litellm-dashboard/src/components/ModelSelect/ModelSelect.tsx
@@ -19,6 +19,7 @@ import {
} from "@/components/ui/combobox";
import { Skeleton } from "@/components/ui/skeleton";
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
+import type { Team } from "@/components/key_team_helpers/key_list";
import { splitWildcardModels } from "./modelUtils";
const MODEL_SELECT_ALL_PROXY_MODELS_SPECIAL_VALUE = {
@@ -76,6 +77,10 @@ type FilterContextArgs = {
const isUncappedModelCeiling = (organizationModels: string[]) =>
organizationModels.length === 0 || organizationModels.includes(MODEL_SELECT_ALL_PROXY_MODELS_SPECIAL_VALUE.value);
+// useTeam seeds from the team list, which omits organization_models; /team/info is the only source of the org ceiling.
+const isAwaitingOrganizationModels = (team: Team | undefined, isFetchingTeam: boolean) =>
+ isFetchingTeam && team !== undefined && team.organization_models === undefined;
+
const contextFilters: Record string[]> = {
user: ({ allProxyModels, userModels, options }) => {
if (!userModels) return [];
@@ -118,13 +123,14 @@ export const ModelSelect = (props: ModelSelectProps) => {
const { id, teamID, organizationID, options, context, dataTestId, value = [], onChange, style } = props;
const { showAllProxyModelsOverride, includeSpecialOptions } = options || {};
const { data: allProxyModels, isLoading: isLoadingAllProxyModels } = useAllProxyModels();
- const { data: team, isLoading: isLoadingTeam } = useTeam(teamID);
+ const { data: team, isLoading: isLoadingTeam, isFetching: isFetchingTeam } = useTeam(teamID);
const { data: organization, isLoading: isLoadingOrganization } = useOrganization(organizationID);
const { data: currentUser, isLoading: isCurrentUserLoading } = useCurrentUser();
const isSpecialOption = (value: string) => MODEL_SENTINEL_OPTIONS.some((sv) => sv.value === value);
const hasSpecialOptionSelected = value.some(isSpecialOption);
- const isLoading = isLoadingAllProxyModels || isLoadingTeam || isLoadingOrganization || isCurrentUserLoading;
+ const isTeamPending = isLoadingTeam || isAwaitingOrganizationModels(team, isFetchingTeam);
+ const isLoading = isLoadingAllProxyModels || isTeamPending || isLoadingOrganization || isCurrentUserLoading;
// The org's ceiling rides on /team/info, which a team admin may read; /organization/info 403s for them.
const organizationModels = team?.organization_models ?? organization?.models;
const organizationHasAllProxyModels = organizationModels !== undefined && isUncappedModelCeiling(organizationModels);