langchain-ai/deepagents · error · OSError
Path is not a directory: {path}
Error message
Path is not a directory: {path} What it means
`_harden_dir` in offload.py creates a directory and then verifies it is actually a directory via `lstat`. If the path exists but is a file, symlink-to-file, socket, etc., it raises `OSError` with 'Path is not a directory'. This protects data directories for conversation data and offloaded tool results.
Source
Thrown at libs/code/deepagents_code/offload.py:101
"""Create `path` if needed and restrict it to the current user.
Only ever call this on directories owned by this process's storage (a temp
dir or a dedicated subdirectory), never on the shared `~/.deepagents` config
root.
Args:
path: Directory to create and harden to `0o700`.
Raises:
OSError: If the path exists but is not a directory, or the directory
cannot be created or its mode changed (e.g. a read-only mount).
PermissionError: If the existing directory is owned by another local user.
"""
path.mkdir(mode=0o700, parents=True, exist_ok=True)
info = path.lstat()
if not stat.S_ISDIR(info.st_mode):
msg = f"Path is not a directory: {path}"
raise OSError(msg)
getuid = getattr(os, "getuid", None)
if getuid is not None and info.st_uid != getuid():
msg = f"Directory is owned by another user: {path}"
raise PermissionError(msg)
# `mkdir(mode=...)` does not tighten an existing directory. These directories
# can hold conversation data and offloaded tool results, so they must remain
# inaccessible to other local accounts regardless of the process umask.
path.chmod(0o700)
def _probe_writable(path: Path) -> None:
"""Confirm `path` accepts new files (catches read-only mounts).
Creating the directory is insufficient when it already exists on a read-only
mount; a temporary file proves writes can succeed.
Args:
path: Directory to probe.View on GitHub (pinned to a1af029e6e)
Solutions
- Inspect the path with `ls -la` and remove or rename the offending non-directory entry
- Re-create it as a directory: `mkdir -p <path>`
- If it's a symlink, fix or remove the symlink target
Example fix
# before (file blocks the dir) ls -la ~/.deepagents/artifacts # -rw-r--r-- file # after rm ~/.deepagents/artifacts && mkdir -p ~/.deepagents/artifacts
Defensive patterns
Strategy: validation
Validate before calling
p = Path(path)
if p.exists() and not p.is_dir():
raise RuntimeError(f"Expected a directory at {p}, found {p.stat().st_size}-byte file; remove it first") Type guard
def is_dir(p: Path) -> bool:
return p.exists() and p.is_dir() Try / catch
try:
root = _artifacts_root()
except OSError as e:
logging.error("state dir unusable: %s", e)
raise SystemExit(1) Prevention
- Don't create files in directories reserved for the tool's state
- Check for stray files/symlinks after migrations or container mounts
- Use a dedicated XDG state dir to avoid collisions
When it happens
Trigger: A regular file exists at the artifacts/user/temp dir path (e.g. `_artifacts_root`, `_prepare_user_dir`, `_prepare_temp_dir`), so `mkdir(exist_ok=True)` succeeds but `stat.S_ISDIR` fails; or a stale symlink points at a file.
Common situations: A stray file named like the state directory (e.g. created by `touch` or an old tool) in HOME/cache paths; bind mounts or Docker volumes mounting a file where a dir is expected; a symlink replaced by a file during migration.
Related errors
- Directory is owned by another user: {path}
- debug log directory is not owned by the current user: {path}
- Cannot determine whether {str(left)!r} is {str(right)!r}: {e
- Invalid DEEPAGENTS_HOME {str(root)!r}: exists but cannot be
- Failed to read credential file {path}: {exc}. Check the file
AI-assisted analysis of langchain-ai/deepagents@a1af029e6e (2026-08-29).
Data as JSON: /api/errors/a12b0e20f28c4a21.
Report an issue: GitHub.