BerriAI/litellm · error · FileNotFoundError

Blocked words file not found: {file_path}

Error message

Blocked words file not found: {file_path}

What it means

FileNotFoundError re-raised when open(file_path) fails while loading the blocked-words file — the configured path simply does not exist from the proxy process's point of view. The guardrail re-raises with the configured path in the message so you can see exactly what it tried to open.

Source

Thrown at litellm/proxy/guardrails/guardrail_hooks/litellm_content_filter/content_filter.py:810

                data: Final = yaml.safe_load(f)

            if not isinstance(data, dict) or "blocked_words" not in data:
                raise ValueError("Invalid format: file must contain 'blocked_words' key with list of words")

            for word_data in data["blocked_words"]:
                if not isinstance(word_data, dict) or "keyword" not in word_data or "action" not in word_data:
                    verbose_proxy_logger.warning("Skipping invalid word entry: %s", word_data)
                    continue

                keyword = word_data["keyword"].lower()
                action = ContentFilterAction(word_data["action"])
                description = word_data.get("description")

                self.blocked_words[keyword] = (action, description)

            verbose_proxy_logger.info("Loaded %s blocked words from %s", len(data["blocked_words"]), file_path)
        except FileNotFoundError:
            raise FileNotFoundError(f"Blocked words file not found: {file_path}")
        except Exception as e:
            raise Exception(f"Error loading blocked words file {file_path}: {e}")

    def _find_pattern_spans(self, text: str, pattern_entry: CompiledPatternEntry) -> list[tuple[int, int]]:
        """Return all match spans for a pattern, applying contextual rules if required."""

        regex: Final[Pattern[str]] = pattern_entry["regex"]
        keyword_regex: Final[Pattern[str] | None] = pattern_entry.get("keyword_regex")
        allow_word_numbers: Final[bool] = pattern_entry.get("allow_word_numbers", False)

        keyword_matches: Final = list(keyword_regex.finditer(text)) if keyword_regex is not None else None
        if keyword_matches is not None and not keyword_matches:
            return []

        match_spans: Final[list[tuple[int, int]]] = []

        for match in regex.finditer(text):
            if keyword_matches is not None and not self._match_near_keyword(

View on GitHub (pinned to 77b7c6c40c)

Solutions

  1. Use an absolute path for blocked_words_file in config.yaml.
  2. In Docker, mount or COPY the file into the image and reference the in-container absolute path.
  3. Verify existence as the proxy user: ls -l /path/to/blocked_words.yaml inside the container.
  4. Add a startup validation step that os.path.isfile()s every configured file before serving traffic.

Example fix

# before (relative — breaks when CWD differs)
litellm_params:
  blocked_words_file: ./blocked_words.yaml

# after (absolute, mounted into the container)
litellm_params:
  blocked_words_file: /etc/litellm/blocked_words.yaml
Defensive patterns

Strategy: validation

Validate before calling

# Startup: every configured file path must exist (absolute paths preferred)
import os

def validate_file_paths(config: dict) -> list[str]:
    missing = []
    for g in config.get("litellm_settings", {}).get("guardrails", []):
        fp = g.get("litellm_params", {}).get("blocked_words_file")
        if fp and not os.path.isfile(fp):
            missing.append(f"blocked_words_file not found: {fp} (cwd={os.getcwd()})")
    return missing

Prevention

When it happens

Trigger: litellm_params.blocked_words_file contains a relative path that doesn't resolve from the proxy's CWD (common in Docker or installed-package deployments), an absolute path that isn't mounted into the container, or a typo'd/nonexistent file.

Common situations: Works locally (CWD = project root) then fails in Docker where the file was never COPY'd/mounted; relative path like ./config/blocked.yaml resolving differently in production; file renamed or moved.

Understand the failure class

Background: "File not found" and ENOENT errors: why libraries can't find a file that should exist — this error's family across 50 libraries.

Related errors


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