BerriAI/litellm · error · HTTPException

Blocked by custom code guardrail

Error message

Blocked by custom code guardrail

What it means

HTTP 400 raised by the custom-code guardrail when the user function's verdict is a block on a response (post-call): detail carries the block reason ('Blocked by custom code guardrail' unless a custom reason was given), the guardrail name, and detection_info. The same verdict pre-call instead raises a passthrough exception that returns a synthetic response to the client, so this 400 shape is specifically the post-call path.

Source

Thrown at litellm/proxy/guardrails/guardrail_hooks/custom_code/custom_code_guardrail.py:342

            reason: Final = result.get("reason", "Blocked by custom code guardrail")
            detection_info: Final = result.get("detection_info", {})

            verbose_proxy_logger.info(
                "Custom code guardrail '%s': Blocking %s - %s", self.guardrail_name, input_type, reason
            )

            is_output: Final = input_type == "response"

            # For pre-call, raise passthrough exception to return synthetic response
            if not is_output:
                self.raise_passthrough_exception(
                    violation_message=reason,
                    request_data=request_data,
                    detection_info=detection_info,
                )

            # For post-call, raise HTTP exception
            raise HTTPException(
                status_code=400,
                detail={
                    "error": reason,
                    "guardrail": self.guardrail_name,
                    "detection_info": detection_info,
                },
            )

        elif action == "modify":
            verbose_proxy_logger.debug("Custom code guardrail '%s': Modifying %s", self.guardrail_name, input_type)

            # Apply modifications
            modified_inputs: Final = dict(inputs)

            if "texts" in result and result["texts"] is not None:
                modified_inputs["texts"] = result["texts"]

            if "images" in result and result["images"] is not None:

View on GitHub (pinned to 77b7c6c40c)

Solutions

  1. Treat as expected behavior: surface detail.error and detail.detection_info to the caller; do not retry — the block is deterministic for the same output
  2. Tune the custom rule if it over-blocks (widen allow patterns, narrow the regex)
  3. Move the check to a pre-call hook if you want a synthetic response instead of a post-call 400
  4. Use the block reason string (block('reason')) so clients get an actionable message instead of the generic default

Example fix

# custom_code: before — default generic reason
return block()

# custom_code: after — actionable reason
return block('Response contained a customer account number')

# caller: handle the 400 explicitly
try:
    out = await client.chat.completions.create(**params)
except Exception as e:
    body = getattr(getattr(e, 'response', None), 'json', lambda: {})() or {}
    err = body.get('error', {})
    if err and body.get('guardrail') == 'my-custom-guardrail':
        return blocked_to_user(err, body.get('detection_info'))
    raise
Defensive patterns

Strategy: try-catch

Try / catch

try:
    out = await client.chat.completions.create(**params)
except Exception as e:
    body = getattr(getattr(e, 'response', None), 'json', lambda: {})() or {}
    err = body.get('error')
    if isinstance(err, str) and body.get('guardrail'):
        return blocked_to_user(err, body.get('detection_info'))  # verdict, not a bug; no retry
    raise

Prevention

When it happens

Trigger: apply_guardrail returns block(...) while input_type == 'response' — the model output matched the custom rule (e.g. PII leaked in a completion, or a response-rejection phrase template); the post-call hook converts the verdict to a 400 for the caller.

Common situations: Output filters for refusal phrases or PII in completions (including the shipped RESPONSE_REJECTION_GUARDRAIL_CODE); clients see a 400 whose detail.guardrail names the custom guardrail and mistake it for a bug rather than a verdict.

Related errors


AI-assisted analysis of BerriAI/litellm@77b7c6c40c (2026-08-18). Data as JSON: /api/errors/3a7dabc21a174dfc. Report an issue: GitHub.