{"record":{"id":"aa5b3e6d867cc952","repo":"BerriAI/litellm","slug":"blocked-words-file-not-found-file-path","errorCode":null,"errorMessage":"Blocked words file not found: {file_path}","messagePattern":"Blocked words file not found: (.+?)","errorType":"exception","errorClass":"FileNotFoundError","httpStatus":null,"severity":"error","filePath":"litellm/proxy/guardrails/guardrail_hooks/litellm_content_filter/content_filter.py","lineNumber":810,"sourceCode":"                data: Final = yaml.safe_load(f)\n\n            if not isinstance(data, dict) or \"blocked_words\" not in data:\n                raise ValueError(\"Invalid format: file must contain 'blocked_words' key with list of words\")\n\n            for word_data in data[\"blocked_words\"]:\n                if not isinstance(word_data, dict) or \"keyword\" not in word_data or \"action\" not in word_data:\n                    verbose_proxy_logger.warning(\"Skipping invalid word entry: %s\", word_data)\n                    continue\n\n                keyword = word_data[\"keyword\"].lower()\n                action = ContentFilterAction(word_data[\"action\"])\n                description = word_data.get(\"description\")\n\n                self.blocked_words[keyword] = (action, description)\n\n            verbose_proxy_logger.info(\"Loaded %s blocked words from %s\", len(data[\"blocked_words\"]), file_path)\n        except FileNotFoundError:\n            raise FileNotFoundError(f\"Blocked words file not found: {file_path}\")\n        except Exception as e:\n            raise Exception(f\"Error loading blocked words file {file_path}: {e}\")\n\n    def _find_pattern_spans(self, text: str, pattern_entry: CompiledPatternEntry) -> list[tuple[int, int]]:\n        \"\"\"Return all match spans for a pattern, applying contextual rules if required.\"\"\"\n\n        regex: Final[Pattern[str]] = pattern_entry[\"regex\"]\n        keyword_regex: Final[Pattern[str] | None] = pattern_entry.get(\"keyword_regex\")\n        allow_word_numbers: Final[bool] = pattern_entry.get(\"allow_word_numbers\", False)\n\n        keyword_matches: Final = list(keyword_regex.finditer(text)) if keyword_regex is not None else None\n        if keyword_matches is not None and not keyword_matches:\n            return []\n\n        match_spans: Final[list[tuple[int, int]]] = []\n\n        for match in regex.finditer(text):\n            if keyword_matches is not None and not self._match_near_keyword(","sourceCodeStart":792,"sourceCodeEnd":828,"githubUrl":"https://github.com/BerriAI/litellm/blob/77b7c6c40c0c5aa5fbcb1d6a1825ac39ca8829b8/litellm/proxy/guardrails/guardrail_hooks/litellm_content_filter/content_filter.py#L792-L828","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Use an absolute path for blocked_words_file in config.yaml.","In Docker, mount or COPY the file into the image and reference the in-container absolute path.","Verify existence as the proxy user: ls -l /path/to/blocked_words.yaml inside the container.","Add a startup validation step that os.path.isfile()s every configured file before serving traffic."],"exampleFix":"# before (relative — breaks when CWD differs)\nlitellm_params:\n  blocked_words_file: ./blocked_words.yaml\n\n# after (absolute, mounted into the container)\nlitellm_params:\n  blocked_words_file: /etc/litellm/blocked_words.yaml","handlingStrategy":"validation","validationCode":"# Startup: every configured file path must exist (absolute paths preferred)\nimport os\n\ndef validate_file_paths(config: dict) -> list[str]:\n    missing = []\n    for g in config.get(\"litellm_settings\", {}).get(\"guardrails\", []):\n        fp = g.get(\"litellm_params\", {}).get(\"blocked_words_file\")\n        if fp and not os.path.isfile(fp):\n            missing.append(f\"blocked_words_file not found: {fp} (cwd={os.getcwd()})\")\n    return missing","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Use absolute paths in config — relative paths resolve against the proxy's CWD, which differs between dev and Docker.","COPY or mount blocked-words files into the container and reference the in-container absolute path.","Run the path validation above in a startup hook or CI check."],"tags":["filesystem","configuration","docker","content-filter"],"backgroundTag":"file-not-found","analyzedSha":"77b7c6c40c0c5aa5fbcb1d6a1825ac39ca8829b8","analyzedAt":"2026-08-18T11:44:31.656Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}