From 013be3eb04e617359e56a462de3ff00d083da87a Mon Sep 17 00:00:00 2001 From: yucheng-berri Date: Fri, 23 Oct 2026 12:52:27 -0700 Subject: [PATCH] feat(proxy): add LITELLM_DISABLE_LATEST_RELEASE_CHECK to stop the release check (#48065) * feat(proxy): add LITELLM_DISABLE_LATEST_RELEASE_CHECK to stop the release check The dashboard asks the proxy for the latest LiteLLM release, and the proxy fetches it from api.github.com on every cache miss. Deployments that block or audit outbound traffic had no way to turn that off. With LITELLM_DISABLE_LATEST_RELEASE_CHECK=true the endpoint returns null without making a request, which the dashboard already treats as no upgrade banner. * fix(proxy): regenerate dashboard types for the release check flag The endpoint docstring feeds the OpenAPI description, so the ui-api-types check needs schema.d.ts regenerated. The docstring now says the flag skips every outbound request, which stays true once the changelog feed is read too --------- Co-authored-by: Sujit Nirmal Co-authored-by: yucheng-berri <293926326+yucheng-berri@users.noreply.github.com> --- .../latest_release_endpoints.py | 9 +++++++++ .../test_latest_release_endpoints.py | 19 +++++++++++++++++++ ui/litellm-dashboard/src/lib/http/schema.d.ts | 1 + 3 files changed, 29 insertions(+) diff --git a/litellm/proxy/ui_crud_endpoints/latest_release_endpoints.py b/litellm/proxy/ui_crud_endpoints/latest_release_endpoints.py index ef2a5f88e4e..14d75633100 100644 --- a/litellm/proxy/ui_crud_endpoints/latest_release_endpoints.py +++ b/litellm/proxy/ui_crud_endpoints/latest_release_endpoints.py @@ -1,4 +1,5 @@ import asyncio +import os import re import xml.etree.ElementTree as ET from collections import Counter @@ -88,6 +89,10 @@ def _default_fetch_lock() -> asyncio.Lock: return _latest_release_fetch_lock +def _default_release_check_enabled() -> bool: + return os.getenv("LITELLM_DISABLE_LATEST_RELEASE_CHECK", "").strip().lower() != "true" + + def _bucket_for(line: str) -> _Bucket | None: if _NEW_CONTRIBUTOR_PATTERN.match(line) is not None: return None @@ -224,12 +229,16 @@ async def latest_release_info( client: Annotated[_AsyncGetClient, Depends(_default_client)], cache: Annotated[InMemoryCache, Depends(_default_cache)], fetch_lock: Annotated[asyncio.Lock, Depends(_default_fetch_lock)], + release_check_enabled: Annotated[bool, Depends(_default_release_check_enabled)], ) -> LatestReleaseInfo | None: """ Newest stable release on the LiteLLM changelog, linked to its changelog page, with the PR count of its whole minor line (e.g. v1.105.0 + v1.105.1) from GitHub split into new features, bug fixes and other updates. Returns null when the changelog or GitHub can't be reached so the dashboard upgrade banner simply doesn't render. + Set LITELLM_DISABLE_LATEST_RELEASE_CHECK=true to return null without making any outbound request. """ + if not release_check_enabled: + return None result: Final = await get_latest_release_info(client=client, cache=cache, fetch_lock=fetch_lock) if isinstance(result, LatestReleaseUnavailable): verbose_proxy_logger.warning("LiteLLM: latest release info unavailable: %s", result.reason) diff --git a/tests/unit/proxy/ui_crud_endpoints/test_latest_release_endpoints.py b/tests/unit/proxy/ui_crud_endpoints/test_latest_release_endpoints.py index 47f81fb55d1..d5139263dc3 100644 --- a/tests/unit/proxy/ui_crud_endpoints/test_latest_release_endpoints.py +++ b/tests/unit/proxy/ui_crud_endpoints/test_latest_release_endpoints.py @@ -401,6 +401,25 @@ class TestLatestReleaseInfoEndpoint: assert response.status_code == 200 assert response.json() is None + @pytest.mark.parametrize( + ("flag", "expected_body", "expected_urls"), + [ + ("true", None, []), + (" TRUE ", None, []), + ("false", EXPECTED_INFO, [CHANGELOG_FEED_URL, _tag_url("v1.102.0")]), + ], + ) + def test_disable_flag_skips_every_outbound_request( + self, http_client, monkeypatch, flag, expected_body, expected_urls + ): + monkeypatch.setenv("LITELLM_DISABLE_LATEST_RELEASE_CHECK", flag) + client = _minor_release() + _override_dependencies(client, _fresh_cache(), LitellmUserRoles.PROXY_ADMIN) + response = http_client.get("/get/latest_release_info") + assert response.status_code == 200 + assert response.json() == expected_body + assert client.urls == expected_urls + def test_repeated_requests_reuse_cache(self, http_client): client = _minor_release() _override_dependencies(client, _fresh_cache(), LitellmUserRoles.PROXY_ADMIN) diff --git a/ui/litellm-dashboard/src/lib/http/schema.d.ts b/ui/litellm-dashboard/src/lib/http/schema.d.ts index f3821a2bb46..b27d06b00f0 100644 --- a/ui/litellm-dashboard/src/lib/http/schema.d.ts +++ b/ui/litellm-dashboard/src/lib/http/schema.d.ts @@ -5868,6 +5868,7 @@ export interface paths { * @description Newest stable release on the LiteLLM changelog, linked to its changelog page, with the PR count of its whole * minor line (e.g. v1.105.0 + v1.105.1) from GitHub split into new features, bug fixes and other updates. * Returns null when the changelog or GitHub can't be reached so the dashboard upgrade banner simply doesn't render. + * Set LITELLM_DISABLE_LATEST_RELEASE_CHECK=true to return null without making any outbound request. */ get: operations["latest_release_info_get_latest_release_info_get"]; put?: never;