From 3a15e1cc2ef68ba96abe9ad7b03e627cb21f8da4 Mon Sep 17 00:00:00 2001 From: yuneng-jiang Date: Fri, 6 Mar 2026 21:24:49 -0800 Subject: [PATCH 1/3] [Fix] Block proxy startup when use_redis_transaction_buffer is enabled without Redis cache When `use_redis_transaction_buffer: true` is set in general_settings but no Redis cache is configured in litellm_settings, the proxy starts successfully but silently drops all spend tracking data. This adds a startup validation that raises a clear error, preventing the proxy from running in a broken state. Co-Authored-By: Claude Opus 4.6 --- litellm/proxy/proxy_server.py | 40 +++++++++++++++ .../test_redis_update_buffer.py | 51 +++++++++++++++++++ 2 files changed, 91 insertions(+) diff --git a/litellm/proxy/proxy_server.py b/litellm/proxy/proxy_server.py index b7f11ec270..d0d132c313 100644 --- a/litellm/proxy/proxy_server.py +++ b/litellm/proxy/proxy_server.py @@ -871,6 +871,12 @@ async def proxy_startup_event(app: FastAPI): # noqa: PLR0915 redis_usage_cache=redis_usage_cache, ) + ## Validate use_redis_transaction_buffer requires Redis cache ## + ProxyStartupEvent._validate_redis_transaction_buffer_config( + general_settings=general_settings, + redis_usage_cache=redis_usage_cache, + ) + ## SEMANTIC TOOL FILTER ## # Read litellm_settings from config for semantic filter initialization try: @@ -5618,6 +5624,40 @@ class ProxyStartupEvent: llm_router=llm_router, redis_usage_cache=redis_usage_cache ) + @staticmethod + def _validate_redis_transaction_buffer_config( + general_settings: dict, + redis_usage_cache: Optional[RedisCache], + ): + """ + Validates that when use_redis_transaction_buffer is enabled, + a Redis cache is properly configured in litellm_settings. + + Without Redis, spend updates are silently dropped because: + - In-memory queues are drained but never pushed to Redis + - The pod lock manager cannot acquire locks for DB commits + - No fallback to direct DB writes occurs + """ + from litellm.proxy.db.db_transaction_queue.redis_update_buffer import ( + RedisUpdateBuffer, + ) + + if ( + RedisUpdateBuffer._should_commit_spend_updates_to_redis() + and redis_usage_cache is None + ): + raise ValueError( + "`use_redis_transaction_buffer` is enabled in general_settings, " + "but no Redis cache is configured. Spend tracking will silently " + "fail without Redis. Please add a Redis cache configuration in " + "litellm_settings:\n\n" + "litellm_settings:\n" + " cache: true\n" + " cache_params:\n" + " type: redis\n" + " url: os.environ/REDIS_URL\n" + ) + @classmethod async def _initialize_semantic_tool_filter( cls, diff --git a/tests/test_litellm/proxy/db/db_transaction_queue/test_redis_update_buffer.py b/tests/test_litellm/proxy/db/db_transaction_queue/test_redis_update_buffer.py index 2a380370c3..a77b8d49b6 100644 --- a/tests/test_litellm/proxy/db/db_transaction_queue/test_redis_update_buffer.py +++ b/tests/test_litellm/proxy/db/db_transaction_queue/test_redis_update_buffer.py @@ -10,6 +10,7 @@ sys.path.insert( ) # Adds the parent directory to the system path from litellm.proxy.db.db_transaction_queue.redis_update_buffer import RedisUpdateBuffer +from litellm.proxy.proxy_server import ProxyStartupEvent from litellm.types.caching import RedisPipelineRpushOperation @@ -192,3 +193,53 @@ async def test_get_all_transactions_from_redis_buffer_pipeline_no_redis(): buffer = RedisUpdateBuffer(redis_cache=None) result = await buffer.get_all_transactions_from_redis_buffer_pipeline() assert result == (None, None, None, None, None, None, None) + + +def test_validate_redis_transaction_buffer_raises_without_redis(): + """ + When use_redis_transaction_buffer=true but no Redis cache is configured, + the proxy should refuse to start with a clear error message. + """ + general_settings = {"use_redis_transaction_buffer": True} + + with patch( + "litellm.proxy.proxy_server.general_settings", general_settings + ): + with pytest.raises(ValueError, match="use_redis_transaction_buffer"): + ProxyStartupEvent._validate_redis_transaction_buffer_config( + general_settings=general_settings, + redis_usage_cache=None, + ) + + +def test_validate_redis_transaction_buffer_passes_with_redis(): + """ + When use_redis_transaction_buffer=true and Redis cache is configured, + validation should pass without error. + """ + general_settings = {"use_redis_transaction_buffer": True} + mock_redis_cache = MagicMock() + + with patch( + "litellm.proxy.proxy_server.general_settings", general_settings + ): + # Should not raise + ProxyStartupEvent._validate_redis_transaction_buffer_config( + general_settings=general_settings, + redis_usage_cache=mock_redis_cache, + ) + + +def test_validate_redis_transaction_buffer_passes_when_disabled(): + """ + When use_redis_transaction_buffer is not set or false, + validation should pass regardless of Redis configuration. + """ + with patch( + "litellm.proxy.proxy_server.general_settings", {} + ): + # Should not raise even without Redis + ProxyStartupEvent._validate_redis_transaction_buffer_config( + general_settings={}, + redis_usage_cache=None, + ) From 9d9a59190c470c6fd905f517fb531a6a8060d9ec Mon Sep 17 00:00:00 2001 From: yuneng-jiang Date: Fri, 6 Mar 2026 21:30:52 -0800 Subject: [PATCH 2/3] Use passed general_settings parameter instead of global import The validation method now reads use_redis_transaction_buffer directly from the passed general_settings dict rather than delegating to RedisUpdateBuffer._should_commit_spend_updates_to_redis() which imports the global. Tests simplified to remove unnecessary patching. Co-Authored-By: Claude Opus 4.6 --- litellm/proxy/proxy_server.py | 15 +++--- .../test_redis_update_buffer.py | 46 +++++++------------ 2 files changed, 24 insertions(+), 37 deletions(-) diff --git a/litellm/proxy/proxy_server.py b/litellm/proxy/proxy_server.py index d0d132c313..d8304cabd5 100644 --- a/litellm/proxy/proxy_server.py +++ b/litellm/proxy/proxy_server.py @@ -5638,14 +5638,15 @@ class ProxyStartupEvent: - The pod lock manager cannot acquire locks for DB commits - No fallback to direct DB writes occurs """ - from litellm.proxy.db.db_transaction_queue.redis_update_buffer import ( - RedisUpdateBuffer, - ) + from litellm.secret_managers.main import str_to_bool - if ( - RedisUpdateBuffer._should_commit_spend_updates_to_redis() - and redis_usage_cache is None - ): + _use_redis_transaction_buffer: Optional[Union[bool, str]] = ( + general_settings.get("use_redis_transaction_buffer", False) + ) + if isinstance(_use_redis_transaction_buffer, str): + _use_redis_transaction_buffer = str_to_bool(_use_redis_transaction_buffer) + + if _use_redis_transaction_buffer and redis_usage_cache is None: raise ValueError( "`use_redis_transaction_buffer` is enabled in general_settings, " "but no Redis cache is configured. Spend tracking will silently " diff --git a/tests/test_litellm/proxy/db/db_transaction_queue/test_redis_update_buffer.py b/tests/test_litellm/proxy/db/db_transaction_queue/test_redis_update_buffer.py index a77b8d49b6..78e07c2967 100644 --- a/tests/test_litellm/proxy/db/db_transaction_queue/test_redis_update_buffer.py +++ b/tests/test_litellm/proxy/db/db_transaction_queue/test_redis_update_buffer.py @@ -1,7 +1,7 @@ import json import os import sys -from unittest.mock import AsyncMock, MagicMock, patch +from unittest.mock import AsyncMock, MagicMock import pytest @@ -200,16 +200,11 @@ def test_validate_redis_transaction_buffer_raises_without_redis(): When use_redis_transaction_buffer=true but no Redis cache is configured, the proxy should refuse to start with a clear error message. """ - general_settings = {"use_redis_transaction_buffer": True} - - with patch( - "litellm.proxy.proxy_server.general_settings", general_settings - ): - with pytest.raises(ValueError, match="use_redis_transaction_buffer"): - ProxyStartupEvent._validate_redis_transaction_buffer_config( - general_settings=general_settings, - redis_usage_cache=None, - ) + with pytest.raises(ValueError, match="use_redis_transaction_buffer"): + ProxyStartupEvent._validate_redis_transaction_buffer_config( + general_settings={"use_redis_transaction_buffer": True}, + redis_usage_cache=None, + ) def test_validate_redis_transaction_buffer_passes_with_redis(): @@ -217,17 +212,11 @@ def test_validate_redis_transaction_buffer_passes_with_redis(): When use_redis_transaction_buffer=true and Redis cache is configured, validation should pass without error. """ - general_settings = {"use_redis_transaction_buffer": True} - mock_redis_cache = MagicMock() - - with patch( - "litellm.proxy.proxy_server.general_settings", general_settings - ): - # Should not raise - ProxyStartupEvent._validate_redis_transaction_buffer_config( - general_settings=general_settings, - redis_usage_cache=mock_redis_cache, - ) + # Should not raise + ProxyStartupEvent._validate_redis_transaction_buffer_config( + general_settings={"use_redis_transaction_buffer": True}, + redis_usage_cache=MagicMock(), + ) def test_validate_redis_transaction_buffer_passes_when_disabled(): @@ -235,11 +224,8 @@ def test_validate_redis_transaction_buffer_passes_when_disabled(): When use_redis_transaction_buffer is not set or false, validation should pass regardless of Redis configuration. """ - with patch( - "litellm.proxy.proxy_server.general_settings", {} - ): - # Should not raise even without Redis - ProxyStartupEvent._validate_redis_transaction_buffer_config( - general_settings={}, - redis_usage_cache=None, - ) + # Should not raise even without Redis + ProxyStartupEvent._validate_redis_transaction_buffer_config( + general_settings={}, + redis_usage_cache=None, + ) From b70ba3e6ed89a7a64743260c03a0eacb3656d429 Mon Sep 17 00:00:00 2001 From: yuneng-jiang Date: Fri, 6 Mar 2026 21:59:44 -0800 Subject: [PATCH 3/3] Update error message for missing Redis config Co-Authored-By: Claude Opus 4.6 --- litellm/proxy/proxy_server.py | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/litellm/proxy/proxy_server.py b/litellm/proxy/proxy_server.py index d8304cabd5..db0bb735ba 100644 --- a/litellm/proxy/proxy_server.py +++ b/litellm/proxy/proxy_server.py @@ -5632,11 +5632,6 @@ class ProxyStartupEvent: """ Validates that when use_redis_transaction_buffer is enabled, a Redis cache is properly configured in litellm_settings. - - Without Redis, spend updates are silently dropped because: - - In-memory queues are drained but never pushed to Redis - - The pod lock manager cannot acquire locks for DB commits - - No fallback to direct DB writes occurs """ from litellm.secret_managers.main import str_to_bool @@ -5648,10 +5643,9 @@ class ProxyStartupEvent: if _use_redis_transaction_buffer and redis_usage_cache is None: raise ValueError( - "`use_redis_transaction_buffer` is enabled in general_settings, " - "but no Redis cache is configured. Spend tracking will silently " - "fail without Redis. Please add a Redis cache configuration in " - "litellm_settings:\n\n" + "`use_redis_transaction_buffer` is enabled in general_settings " + "but no Redis cache is configured. This will cause spend updates " + "to not be tracked. Add a Redis cache in litellm_settings:\n\n" "litellm_settings:\n" " cache: true\n" " cache_params:\n"