From a5e5dcb1a6e5a408014b460fb65206bdb34aee0d Mon Sep 17 00:00:00 2001 From: Will Date: Fri, 10 Jul 2026 15:41:03 +0100 Subject: [PATCH] fix(guardrails): preserve HTTPException status code in create_guardrail create_guardrail() raised a 400 for invalid guardrail config after rolling back the DB write, but the outer `except Exception` caught that HTTPException and re-raised it as a 500, hiding a client-correctable error behind a server error status code. Re-raise HTTPExceptions unmodified, matching the pattern already used elsewhere in this file. --- .../proxy/guardrails/guardrail_endpoints.py | 2 ++ .../guardrails/test_guardrail_endpoints.py | 33 +++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/litellm/proxy/guardrails/guardrail_endpoints.py b/litellm/proxy/guardrails/guardrail_endpoints.py index d3a5d649f17..5c5608b24cb 100644 --- a/litellm/proxy/guardrails/guardrail_endpoints.py +++ b/litellm/proxy/guardrails/guardrail_endpoints.py @@ -374,6 +374,8 @@ async def create_guardrail( ) return result + except HTTPException: + raise except Exception as e: verbose_proxy_logger.exception(f"Error adding guardrail to db: {e}") raise HTTPException(status_code=500, detail=str(e)) diff --git a/tests/test_litellm/proxy/guardrails/test_guardrail_endpoints.py b/tests/test_litellm/proxy/guardrails/test_guardrail_endpoints.py index ce8f0802ae1..d14464ece58 100644 --- a/tests/test_litellm/proxy/guardrails/test_guardrail_endpoints.py +++ b/tests/test_litellm/proxy/guardrails/test_guardrail_endpoints.py @@ -834,6 +834,39 @@ async def test_create_guardrail_endpoint( ) +@pytest.mark.asyncio +async def test_create_guardrail_config_error_returns_400_not_500( + mocker, mock_guardrail_registry, mock_in_memory_handler +): + """ + A ValueError raised while initializing a newly created guardrail is a + client-correctable config error and must surface as a 400. Pre-fix, the + inner 400 HTTPException was caught by the outer `except Exception` and + re-raised as a 500. + """ + mock_prisma_client = mocker.Mock() + mock_in_memory_handler.initialize_guardrail.side_effect = ValueError("invalid config") + + mocker.patch("litellm.proxy.proxy_server.prisma_client", mock_prisma_client) + mocker.patch( + "litellm.proxy.guardrails.guardrail_endpoints.GUARDRAIL_REGISTRY", + mock_guardrail_registry, + ) + mocker.patch( + "litellm.proxy.guardrails.guardrail_registry.IN_MEMORY_GUARDRAIL_HANDLER", + mock_in_memory_handler, + ) + + with pytest.raises(HTTPException) as exc_info: + await create_guardrail(MOCK_CREATE_REQUEST, user_api_key_dict=MOCK_ADMIN_USER) + + assert exc_info.value.status_code == 400 + assert "invalid config" in str(exc_info.value.detail) + mock_guardrail_registry.add_guardrail_to_db.assert_called_once_with( + guardrail=MOCK_CREATE_REQUEST.guardrail, prisma_client=mocker.ANY + ) + + @pytest.mark.parametrize( "scenario,expected_result,expected_exception", [