BerriAI/litellm · error · CustomCodeExecutionError

Custom code guardrail execution failed: {e}

Error message

Custom code guardrail execution failed: {e}

What it means

CustomCodeExecutionError raised when the user's apply_guardrail function itself raises any exception other than the guardrail's own control-flow exceptions (HTTPException from block actions, ModifyResponseException from passthrough). The original exception is chained and details include guardrail_name and input_type — the failure is in the custom code's runtime logic, not in compilation or the sandbox.

Source

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

                result = await result

            # Process the result
            return self._process_result(
                result=result,
                inputs=inputs,
                request_data=request_data,
                input_type=input_type,
            )

        except HTTPException:
            # Re-raise HTTP exceptions (from block action)
            raise
        except ModifyResponseException:
            # Pre-call block uses passthrough; must not wrap as execution error (500)
            raise
        except Exception as e:
            verbose_proxy_logger.error("Custom code guardrail '%s' execution error: %s", self.guardrail_name, e)
            raise CustomCodeExecutionError(
                f"Custom code guardrail execution failed: {e}",
                details={
                    "guardrail_name": self.guardrail_name,
                    "input_type": input_type,
                },
            ) from e

    def _prepare_safe_request_data(self, request_data: dict) -> dict[str, Any]:
        """
        Prepare a safe subset of request_data for code execution.

        This filters out sensitive information and provides only what's
        needed for guardrail logic.

        Args:
            request_data: The full request data

        Returns:

View on GitHub (pinned to 77b7c6c40c)

Solutions

  1. Read the wrapped message and __cause__ — they contain the original error from your function
  2. Access inputs defensively (inputs.get('texts') or []) and wrap sandbox HTTP calls in try/except inside the custom code
  3. Unit-test the exact custom_code string against representative inputs outside the proxy before deploying
  4. Decide fail-open vs fail-closed explicitly: return allow() on unexpected internal errors only if skipping the check is acceptable

Example fix

# before: crashes on shape assumptions and HTTP failures
async def apply_guardrail(inputs, request_data, input_type):
    text = inputs['texts'][0]
    r = await http_post('https://mod.example/check', body={'text': text})
    if r['body']['flagged']:
        return block('flagged')
    return allow()

# after: guarded access, explicit failure policy
async def apply_guardrail(inputs, request_data, input_type):
    for text in (inputs or {}).get('texts') or []:
        try:
            r = await http_post('https://mod.example/check', body={'text': text})
        except Exception:
            return allow()  # fail-open; use block(...) if you need fail-closed
        if (r.get('body') or {}).get('flagged'):
            return block('flagged by moderation API')
    return allow()
Defensive patterns

Strategy: try-catch

Validate before calling

from litellm.proxy.guardrails.guardrail_hooks.custom_code.sandbox import (
    build_sandbox_globals, compile_sandboxed,
)

# exercise the entry point with representative inputs before deploying
def smoke(custom_code: str) -> None:
    g = build_sandbox_globals()
    exec(compile_sandboxed(custom_code), g)
    fn = g['apply_guardrail']
    out = fn({'texts': ['hello'], 'messages': []}, {'metadata': {}}, 'request')
    assert out is not None

Try / catch

from litellm.proxy.guardrails.guardrail_hooks.custom_code.custom_code_guardrail import CustomCodeExecutionError
try:
    inputs = await guardrail.apply_guardrail(inputs, request_data, input_type)
except CustomCodeExecutionError as e:
    log.warning('custom guardrail crashed', guardrail=e.details.get('guardrail_name'),
                input_type=e.details.get('input_type'), cause=repr(e.__cause__))
    # deliberate policy choice: re-raise (fail closed) or proceed (fail open)
    raise

Prevention

When it happens

Trigger: Runtime bugs inside the user function on a hooked request: KeyError/TypeError from assuming an inputs shape, NameError for an undefined helper, arithmetic errors, or a sandbox HTTP primitive (http_get/http_post/http_request) raising on timeout/connection failure/status error.

Common situations: inputs['texts'][0] on an empty payload; calling the moderation endpoint without try/except and it 500s; assuming the primitive's return shape differs from the documented {success, status, body} dict; renaming a helper but not all call sites.

Related errors


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