BerriAI/litellm · error · ValueError

Guardrail {guardrail['guardrail_name']}: scan_only_tool_resu

Error message

Guardrail {guardrail['guardrail_name']}: scan_only_tool_results and skip_tool_message_in_guardrail are enabled together, which excludes every message from scanning, so no request content would ever be scanned. Remove one of the two.

What it means

Guardrail init validation: scan_only_tool_results (restrict scanning to tool messages) and skip_tool_message_in_guardrail (exclude tool messages from scanning) are mutually exclusive - together they exclude every message, so no request content would ever be scanned. litellm rejects the combination at startup with this ValueError.

Source

Thrown at litellm/proxy/guardrails/guardrail_registry.py:522

        if custom_guardrail_callback is not None:
            for scoping_param in (
                "skip_system_message_in_guardrail",
                "skip_tool_message_in_guardrail",
                "scan_only_tool_results",
            ):
                setattr(custom_guardrail_callback, scoping_param, getattr(litellm_params, scoping_param, None))
            scan_only_tool_results_enabled: Final = effective_scan_only_tool_results_for_guardrail(
                custom_guardrail_callback
            )
            if scan_only_tool_results_enabled and not custom_guardrail_callback.supports_scan_only_tool_results():
                raise ValueError(
                    f"Guardrail {guardrail['guardrail_name']}: scan_only_tool_results is enabled, but this "
                    "guardrail's role filtering never scans tool results, so no request content would ever "
                    "be scanned. Remove scan_only_tool_results or the guardrail's role-filtering option."
                )
            if scan_only_tool_results_enabled and effective_skip_tool_message_for_guardrail(custom_guardrail_callback):
                raise ValueError(
                    f"Guardrail {guardrail['guardrail_name']}: scan_only_tool_results and "
                    "skip_tool_message_in_guardrail are enabled together, which excludes every message from "
                    "scanning, so no request content would ever be scanned. Remove one of the two."
                )
            configured_run_in_parallel: Final[bool | None] = getattr(litellm_params, "run_in_parallel", None)
            if configured_run_in_parallel is not None:
                custom_guardrail_callback.run_in_parallel = bool(configured_run_in_parallel)

        parsed_guardrail: Final = Guardrail(
            guardrail_id=guardrail.get("guardrail_id"),
            guardrail_name=guardrail["guardrail_name"],
            litellm_params=litellm_params,
            guardrail_info=guardrail.get("guardrail_info"),
        )

        # store references to the guardrail in memory
        self.IN_MEMORY_GUARDRAILS[guardrail_id] = parsed_guardrail
        self.guardrail_id_to_custom_guardrail[guardrail_id] = custom_guardrail_callback

View on GitHub (pinned to 77b7c6c40c)

Solutions

  1. Decide the intent: either scan only tool results (keep scan_only_tool_results, drop skip_tool_message_in_guardrail) or exclude tool messages (the reverse)
  2. Split into two guardrail entries if you need both behaviors for different hooks
  3. Add a config lint step that rejects guardrail entries containing both flags

Example fix

# before
litellm_params:
  guardrail: aim
  scan_only_tool_results: true
  skip_tool_message_in_guardrail: true

# after
litellm_params:
  guardrail: aim
  scan_only_tool_results: true
Defensive patterns

Strategy: validation

Validate before calling

# Hard rule: the two flags are mutually exclusive
import yaml

cfg = yaml.safe_load(open('config.yaml'))
for g in cfg.get('guardrails', []):
    lp = g.get('litellm_params') or {}
    if lp.get('scan_only_tool_results') and lp.get('skip_tool_message_in_guardrail'):
        raise SystemExit(
            f"{g.get('guardrail_name')}: pick ONE of scan_only_tool_results / skip_tool_message_in_guardrail"
        )

Type guard

def has_valid_scoping(litellm_params: dict) -> bool:
    """False when scoping flags would exclude every message from scanning."""
    return not (
        bool(litellm_params.get('scan_only_tool_results'))
        and bool(litellm_params.get('skip_tool_message_in_guardrail'))
    )

Prevention

When it happens

Trigger: A single guardrail entry with both scan_only_tool_results: true and skip_tool_message_in_guardrail: true in litellm_params.

Common situations: Flag accumulated over time as config evolved - one option added to reduce noise, later the other added for tool-result scanning; merging two guardrail configs into one.

Related errors


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