langchain-ai/deepagents · error · ValueError

Path:{full} outside root directory: {self.cwd}

Error message

Path:{full} outside root directory: {self.cwd}

What it means

After resolving a virtual-mode path, the backend verifies the fully resolved (symlink-followed) path sits under the backend root via relative_to. If the resolved path escapes the root — even when the textual path looked safe — ValueError is raised. This catches symlinks that point outside the sandbox.

Source

Thrown at libs/deepagents/deepagents/backends/filesystem.py:213

        Returns:
            Resolved absolute `Path` object.

        Raises:
            ValueError: If path traversal is attempted in `virtual_mode` or if the
                resolved path escapes the root directory.
            OSError: If the path is a symlink loop (`ELOOP`).
        """
        if self.virtual_mode:
            vpath = key if key.startswith("/") else "/" + key
            if ".." in vpath or vpath.startswith("~"):
                msg = "Path traversal not allowed"
                raise ValueError(msg)
            full = (self.cwd / vpath.lstrip("/")).resolve()
            try:
                full.relative_to(self.cwd)
            except ValueError:
                msg = f"Path:{full} outside root directory: {self.cwd}"
                raise ValueError(msg) from None
            _raise_if_symlink_loop(full)
            return full

        path = Path(key)
        if path.is_absolute():
            _raise_if_symlink_loop(path)
            return path
        resolved = (self.cwd / path).resolve()
        _raise_if_symlink_loop(resolved)
        return resolved

    def _to_virtual_path(self, path: Path) -> str:
        """Convert a filesystem path to a virtual path relative to cwd.

        Args:
            path: Filesystem path to convert.

        Returns:

View on GitHub (pinned to a1af029e6e)

Solutions

  1. Inspect the resolved path printed in the message and use one contained within the root directory
  2. Remove or re-point symlinks that escape the backend root
  3. Verify the backend was constructed with the intended root_dir / cwd
  4. Copy external files into the root rather than linking to them

Example fix

// before
backend.read("shared-link/data.txt")  # symlink -> /etc/data.txt
// after
cp /etc/data.txt ./data.txt
backend.read("data.txt")
Defensive patterns

Strategy: validation

Validate before calling

from pathlib import Path
def stays_in_root(root: Path, key: str) -> bool:
    resolved = (root / key.lstrip("/")).resolve()
    return resolved == root or root in resolved.parents

Try / catch

try:
    data = backend.read(key)
except ValueError as exc:
    if str(exc).startswith("Path:") and "outside root directory" in str(exc):
        data = None  # symlink or path escapes sandbox; handle explicitly
    else:
        raise

Prevention

When it happens

Trigger: Calling ls/read/write/edit/delete/grep with a path (or a symlink target) that resolves outside self.cwd, e.g. 'link' pointing to /etc, or a deeply nested '../../' chain that survives normalization.

Common situations: Symlinks created inside the workspace that point to external files, or mounting a workspace where cwd differs from expectations (running from a different working directory).

Related errors


AI-assisted analysis of langchain-ai/deepagents@a1af029e6e (2026-08-29). Data as JSON: /api/errors/87072a1a66a96b6b. Report an issue: GitHub.