crewAIInc/crewAI · error · ValueError
Path '{validated}' is not a directory.
Error message
Path '{validated}' is not a directory. What it means
Raised by validate_directory_path after validate_file_path succeeds but os.path.isdir() fails: the path is inside the allowed base, yet it is not a directory (it is a file, a symlink to a file, or does not exist at all). The check follows symlinks, so a symlink to a directory passes while a dangling one fails.
Source
Thrown at lib/crewai-tools/src/crewai_tools/security/safe_path.py:142
def validate_directory_path(path: str, base_dir: str | None = None) -> str:
"""Validate that a directory path is safe to read.
Same as :func:`validate_file_path` but also checks that the path
is an existing directory.
Args:
path: The directory path to validate.
base_dir: Allowed root directory. Defaults to ``os.getcwd()``.
Returns:
The resolved, validated absolute path.
Raises:
ValueError: If the path escapes the allowed directory or is not a directory.
"""
validated = validate_file_path(path, base_dir)
if not os.path.isdir(validated):
raise ValueError(f"Path '{validated}' is not a directory.")
return validated
# Private and reserved IP ranges that should not be accessed
_BLOCKED_IPV4_NETWORKS = [
ipaddress.ip_network("10.0.0.0/8"),
ipaddress.ip_network("172.16.0.0/12"),
ipaddress.ip_network("192.168.0.0/16"),
ipaddress.ip_network("127.0.0.0/8"),
ipaddress.ip_network("169.254.0.0/16"), # Link-local / cloud metadata
ipaddress.ip_network("0.0.0.0/32"),
]
_BLOCKED_IPV6_NETWORKS = [
ipaddress.ip_network("::1/128"),
ipaddress.ip_network("::/128"),
ipaddress.ip_network("fc00::/7"), # Unique local addresses
ipaddress.ip_network("fe80::/10"), # Link-local IPv6View on GitHub (pinned to 754d7323be)
Solutions
- Verify with os.path.isdir(path) before calling and print the resolved realpath to confirm what you are actually pointing at.
- Create the directory first if it is expected to exist: os.makedirs(path, exist_ok=True) (also resolves the not-yet-created case).
- Check the config value — a file path in a directory-valued setting is the most common cause.
- Ensure the mount/volume is attached before the process starts in containerized deployments.
Example fix
# before
validated = validate_directory_path("/srv/data/report.pdf") # it's a file
# after
import os
path = "/srv/data/reports"
os.makedirs(path, exist_ok=True)
validated = validate_directory_path(path) Defensive patterns
Strategy: validation
Validate before calling
import os
def ensure_directory(path: str, create: bool = False) -> bool:
if create and not os.path.exists(path):
os.makedirs(path, exist_ok=True)
return os.path.isdir(os.path.realpath(path)) Try / catch
try:
validated = validate_directory_path(dir_path)
except ValueError as e:
if "is not a directory" in str(e):
raise ConfigError(f"expected a directory, got file/missing path: {dir_path}") from e
raise Prevention
- Type-check config values: directory settings must end in a directory, not a file.
- Create required directories at startup with os.makedirs(..., exist_ok=True).
- Validate mounts exist before process start in containerized deployments.
When it happens
Trigger: Passing a file path where a directory is expected (e.g. pointing a recursive loader's directory argument at a single document); passing a path that does not exist yet (checked before creation); a dangling symlink; race conditions where the directory is deleted between resolution and the isdir call.
Common situations: Config keys like data_dir/docs_path given a filename by mistake; scripts run before the directory is created; deploy pipelines where the volume is mounted at a different path than configured.
Related errors
- Project name cannot be empty
- Project name '{name}' produces invalid folder name '{folder_
- No deployable project files were found.
- Missing required fields in OAuth2 configuration: [{', '.join
- Invalid --definition path: {definition} is not a file.
AI-assisted analysis of crewAIInc/crewAI@754d7323be (2026-08-15).
Data as JSON: /api/errors/a8930fd9eb16968f.
Report an issue: GitHub.