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 IPv6

View on GitHub (pinned to 754d7323be)

Solutions

  1. Verify with os.path.isdir(path) before calling and print the resolved realpath to confirm what you are actually pointing at.
  2. Create the directory first if it is expected to exist: os.makedirs(path, exist_ok=True) (also resolves the not-yet-created case).
  3. Check the config value — a file path in a directory-valued setting is the most common cause.
  4. 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

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


AI-assisted analysis of crewAIInc/crewAI@754d7323be (2026-08-15). Data as JSON: /api/errors/a8930fd9eb16968f. Report an issue: GitHub.