litellm - 💡(How to fix) Fix [Bug]: generic_guardrail_api blocks logged as guardrail_failed_to_respond instead of guardrail_intervened [1 pull requests]

Official PRs (…)
ON THIS PAGE

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…

Error Message

@staticmethod def _is_guardrail_intervention(e: Exception) -> bool: if isinstance(e, ModifyResponseException): return True if (HTTPException is not None and isinstance(e, HTTPException) and e.status_code == 400): return True return False

Root Cause

_is_guardrail_intervention() in litellm/integrations/custom_guardrail.py only recognizes two exception types as intentional blocks:

@staticmethod
def _is_guardrail_intervention(e: Exception) -> bool:
    if isinstance(e, ModifyResponseException):
        return True
    if (HTTPException is not None
        and isinstance(e, HTTPException)
        and e.status_code == 400):
        return True
    return False

But generic_guardrail_api.py raises GuardrailRaisedException on block — which is not checked by _is_guardrail_intervention. So _process_error falls through to guardrail_failed_to_respond.

Fix Action

Fixed

Code Example

@staticmethod
def _is_guardrail_intervention(e: Exception) -> bool:
    if isinstance(e, ModifyResponseException):
        return True
    if (HTTPException is not None
        and isinstance(e, HTTPException)
        and e.status_code == 400):
        return True
    return False

---

from litellm.exceptions import GuardrailRaisedException

@staticmethod
def _is_guardrail_intervention(e: Exception) -> bool:
    if isinstance(e, GuardrailRaisedException):
        return True
    if isinstance(e, ModifyResponseException):
        return True
    if (HTTPException is not None
        and isinstance(e, HTTPException)
        and e.status_code == 400):
        return True
    return False

---

## Log Output

### Adapter log (block detected correctly)


{
  "_msg": "scan complete",
  "_time": "2026-05-18T13:36:31.139946872Z",
  "log.is_valid": "false",
  "log.level": "INFO",
  "log.msg": "scan complete",
  "log.scanners.PromptInjection": "1"
}


### LiteLLM log (misclassified as error, returns 500)


13:36:31 - LiteLLM Proxy:ERROR: utils.py:5611 - Exception: blocked by: PromptInjection (score: 1.00)
Traceback (most recent call last):
  File ".../proxy/guardrails/guardrail_endpoints.py", line 2233, in apply_guardrail
    guardrailed_inputs = await active_guardrail.apply_guardrail(...)
  File ".../integrations/custom_guardrail.py", line 958, in async_wrapper
    return self._process_error(e=e, ...)
  File ".../integrations/custom_guardrail.py", line 788, in _process_error
    raise e
  File ".../integrations/custom_guardrail.py", line 943, in async_wrapper
    response = await func(*args, **kwargs)
  File ".../proxy/guardrails/guardrail_hooks/generic_guardrail_api/generic_guardrail_api.py", line 481, in apply_guardrail
    raise GuardrailRaisedException(...)
litellm.exceptions.GuardrailRaisedException: blocked by: PromptInjection (score: 1.00)

INFO: 10.244.4.4:39408 - "POST /guardrails/apply_guardrail HTTP/1.1" 500 Internal Server Error


Note: The adapter correctly returns `{"action": "BLOCKED", "blocked_reason": "blocked by: PromptInjection (score: 1.00)"}` with HTTP 200. LiteLLM's `generic_guardrail_api.py` correctly raises `GuardrailRaisedException`. But `_process_error` classifies it as `guardrail_failed_to_respond` because `_is_guardrail_intervention` doesn't check for `GuardrailRaisedException`.
RAW_BUFFERClick to expand / collapse

Check for existing issues

  • I have searched the existing issues and checked that my issue is not a duplicate.

What happened?

When a generic_guardrail_api guardrail blocks a request (returns action: "BLOCKED"), the log_guardrail_information decorator misclassifies the block as guardrail_failed_to_respond instead of guardrail_intervened.

This causes the Guardrails Monitor dashboard to show 0 blocked requests and 100% pass rate even when blocks are actively occurring.

Steps to Reproduce

Root Cause

_is_guardrail_intervention() in litellm/integrations/custom_guardrail.py only recognizes two exception types as intentional blocks:

@staticmethod
def _is_guardrail_intervention(e: Exception) -> bool:
    if isinstance(e, ModifyResponseException):
        return True
    if (HTTPException is not None
        and isinstance(e, HTTPException)
        and e.status_code == 400):
        return True
    return False

But generic_guardrail_api.py raises GuardrailRaisedException on block — which is not checked by _is_guardrail_intervention. So _process_error falls through to guardrail_failed_to_respond.

Expected Behavior

When adapter returns {"action": "BLOCKED", "blocked_reason": "..."}:

  • Status should be guardrail_intervened
  • Dashboard "Blocked Requests" counter should increment
  • Pass rate should reflect actual blocks

Actual Behavior

  • Status is guardrail_failed_to_respond
  • Dashboard shows 0 blocks, 100% pass rate
  • Blocks still happen (HTTP 500 returned to caller) but are invisible to monitoring

Suggested Fix

Add GuardrailRaisedException to _is_guardrail_intervention:

from litellm.exceptions import GuardrailRaisedException

@staticmethod
def _is_guardrail_intervention(e: Exception) -> bool:
    if isinstance(e, GuardrailRaisedException):
        return True
    if isinstance(e, ModifyResponseException):
        return True
    if (HTTPException is not None
        and isinstance(e, HTTPException)
        and e.status_code == 400):
        return True
    return False

Related

  • #24348 / PR #24347 — GuardrailRaisedException missing status_code attribute (returns HTTP 500 instead of 400)
  • Both bugs affect all generic_guardrail_api users

Environment

  • LiteLLM version: 1.85.0
  • Guardrail type: generic_guardrail_api
  • Guardrail backend: LLM Guard via custom adapter

Relevant log output

## Log Output

### Adapter log (block detected correctly)


{
  "_msg": "scan complete",
  "_time": "2026-05-18T13:36:31.139946872Z",
  "log.is_valid": "false",
  "log.level": "INFO",
  "log.msg": "scan complete",
  "log.scanners.PromptInjection": "1"
}


### LiteLLM log (misclassified as error, returns 500)


13:36:31 - LiteLLM Proxy:ERROR: utils.py:5611 - Exception: blocked by: PromptInjection (score: 1.00)
Traceback (most recent call last):
  File ".../proxy/guardrails/guardrail_endpoints.py", line 2233, in apply_guardrail
    guardrailed_inputs = await active_guardrail.apply_guardrail(...)
  File ".../integrations/custom_guardrail.py", line 958, in async_wrapper
    return self._process_error(e=e, ...)
  File ".../integrations/custom_guardrail.py", line 788, in _process_error
    raise e
  File ".../integrations/custom_guardrail.py", line 943, in async_wrapper
    response = await func(*args, **kwargs)
  File ".../proxy/guardrails/guardrail_hooks/generic_guardrail_api/generic_guardrail_api.py", line 481, in apply_guardrail
    raise GuardrailRaisedException(...)
litellm.exceptions.GuardrailRaisedException: blocked by: PromptInjection (score: 1.00)

INFO: 10.244.4.4:39408 - "POST /guardrails/apply_guardrail HTTP/1.1" 500 Internal Server Error


Note: The adapter correctly returns `{"action": "BLOCKED", "blocked_reason": "blocked by: PromptInjection (score: 1.00)"}` with HTTP 200. LiteLLM's `generic_guardrail_api.py` correctly raises `GuardrailRaisedException`. But `_process_error` classifies it as `guardrail_failed_to_respond` because `_is_guardrail_intervention` doesn't check for `GuardrailRaisedException`.

What part of LiteLLM is this about?

Proxy

What LiteLLM version are you on ?

1.85.0

Twitter / LinkedIn details

No response

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

litellm - 💡(How to fix) Fix [Bug]: generic_guardrail_api blocks logged as guardrail_failed_to_respond instead of guardrail_intervened [1 pull requests]