From ac1c69663f115da8d875e2f086398518238e37e3 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Thu, 30 Jul 2026 20:12:50 +0000 Subject: [PATCH] fix(guardrails): keep stable config id collision-safe for same-named guardrails A deterministic per-name id made two config guardrails sharing a guardrail_name collide on the same id and hit the in-memory dedup early-return, so the second was dropped. Use the stable id for the first occurrence and fall back to a unique id on clash, preserving prior multi-registration behavior while keeping the id stable for the normal (unique-name, initialized-once) case. Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- .../proxy/guardrails/guardrail_registry.py | 3 +- .../guardrails/test_guardrail_registry.py | 37 +++++++++++++++++++ 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/litellm/proxy/guardrails/guardrail_registry.py b/litellm/proxy/guardrails/guardrail_registry.py index fe7a2efcbda..2201d59db5c 100644 --- a/litellm/proxy/guardrails/guardrail_registry.py +++ b/litellm/proxy/guardrails/guardrail_registry.py @@ -438,7 +438,8 @@ class InMemoryGuardrailHandler: if provided_id: guardrail_id = provided_id elif source == "config": - guardrail_id = get_stable_config_guardrail_id(guardrail["guardrail_name"]) + stable_id = get_stable_config_guardrail_id(guardrail["guardrail_name"]) + guardrail_id = stable_id if stable_id not in self.IN_MEMORY_GUARDRAILS else str(uuid.uuid4()) else: guardrail_id = str(uuid.uuid4()) guardrail["guardrail_id"] = guardrail_id diff --git a/tests/test_litellm/proxy/guardrails/test_guardrail_registry.py b/tests/test_litellm/proxy/guardrails/test_guardrail_registry.py index 2e1f9f194ac..6e946ec4fdf 100644 --- a/tests/test_litellm/proxy/guardrails/test_guardrail_registry.py +++ b/tests/test_litellm/proxy/guardrails/test_guardrail_registry.py @@ -405,6 +405,43 @@ def test_config_guardrail_id_is_stable_across_boots(): registry_module.guardrail_initializer_registry.pop("stable_id_test", None) +def test_same_name_config_guardrails_in_one_process_get_distinct_ids(): + """ + The deterministic id must not collapse two same-named guardrails registered + in one process into one entry (that would drop the second guardrail via the + early-return dedup). The first occurrence gets the stable id; a later clash + falls back to a unique id so both stay registered. + """ + from litellm.proxy.guardrails import guardrail_registry as registry_module + + def _initializer(litellm_params, guardrail): + return CustomGuardrail( + guardrail_name=guardrail["guardrail_name"], + event_hook=GuardrailEventHooks.pre_call, + default_on=False, + ) + + registry_module.guardrail_initializer_registry["dup_name_test"] = _initializer + try: + handler = InMemoryGuardrailHandler() + params = {"guardrail": "dup_name_test", "mode": "pre_call"} + + first = handler.initialize_guardrail( + guardrail={"guardrail_name": "dup", "litellm_params": dict(params)}, + source="config", + ) + second = handler.initialize_guardrail( + guardrail={"guardrail_name": "dup", "litellm_params": dict(params)}, + source="config", + ) + + assert first["guardrail_id"] == registry_module.get_stable_config_guardrail_id("dup") + assert second["guardrail_id"] != first["guardrail_id"] + assert len(handler.IN_MEMORY_GUARDRAILS) == 2 + finally: + registry_module.guardrail_initializer_registry.pop("dup_name_test", None) + + def test_explicit_config_guardrail_id_is_preserved(): """An operator-provided guardrail_id must win over the derived-from-name id.""" from litellm.proxy.guardrails import guardrail_registry as registry_module