langchain-ai/deepagents · error · DeepAgentsHomeError

Cannot determine whether {str(left)!r} is {str(right)!r}: {e

Error message

Cannot determine whether {str(left)!r} is {str(right)!r}: {exc.strerror or exc}. Fix the permissions on those paths, or set DEEPAGENTS_HOME to a path that can be read.

What it means

_same_directory compares two paths for identity (to reject degenerate profile roots like DEEPAGENTS_HOME=/ or /tmp). If the underlying OS calls raise OSError (e.g. EACCES while stat'ing the paths), it re-raises as DeepAgentsHomeError because the library cannot prove the paths differ. This is a fail-closed guard on the trust boundary.

Source

Thrown at libs/code/deepagents_code/_paths.py:662

    try:
        return Path(left).samefile(right)
    except FileNotFoundError:
        # A profile root that is not there yet cannot be the home directory,
        # and the lexical comparison above has already ruled out the
        # spelling-only case.
        logger.debug("Could not compare %s with %s: one is missing", left, right)
        return False
    except OSError as exc:
        # EACCES, ELOOP, EIO, ESTALE: the answer is unknown, not "different".
        # The whole point of `samefile` here is to catch the non-lexical
        # spellings the comparison above misses, so returning `False` would
        # accept exactly the aliases this guard exists to reject.
        msg = (
            f"Cannot determine whether {str(left)!r} is {str(right)!r}: "
            f"{exc.strerror or exc}. Fix the permissions on those paths, or "
            "set DEEPAGENTS_HOME to a path that can be read."
        )
        raise DeepAgentsHomeError(msg) from exc


def _reject_degenerate_root(root: Path, launch_home: Path | None) -> None:
    """Reject a resolved profile root that would scatter state.

    A profile root is a trust boundary that owns everything beneath it, so it
    must be a directory of its own. The rejected cases all resolve to something
    the user did not mean:

    - The filesystem root, from `DEEPAGENTS_HOME=/` or a `..` chain that walks
      past it, would put credentials in `/.state/auth.json`.
    - The home directory itself, from a `DEEPAGENTS_HOME=~/` typo, would make
      the profile dotenv the user's generic `~/.env` and load it as trusted
      configuration.
    - An existing non-directory cannot hold a profile at all.
    - A root that exists but cannot be read. Every later access fails one file
      at a time, and each failure looks like a first run, so reject it once
      here with the real cause.

View on GitHub (pinned to a1af029e6e)

Solutions

  1. Fix permissions on the path and its parents (chmod/chown so the current user can read+search them).
  2. Point DEEPAGENTS_HOME at a readable, user-owned absolute path.
  3. Avoid placing the profile root under system-restricted directories like / or /root.

Example fix

// before: DEEPAGENTS_HOME=/root/.deepagents as non-root user
// after
export DEEPAGENTS_HOME="$HOME/.deepagents"
Defensive patterns

Strategy: try-catch

Validate before calling

import os
from pathlib import Path
root = Path(os.environ.get('DEEPAGENTS_HOME', ''))
if root.exists():
    for p in (root, *root.parents[:2]):
        if not os.access(p, os.R_OK | os.X_OK):
            raise SystemExit(f'unreadable path in DEEPAGENTS_HOME chain: {p}')

Try / catch

try:
    root = _resolve_profile_root()
except DeepAgentsHomeError as exc:
    if 'Cannot determine whether' in str(exc):
        raise SystemExit(f'Fix permissions for DEEPAGENTS_HOME: {exc}') from exc
    raise

Prevention

When it happens

Trigger: DEEPAGENTS_HOME points at a path whose permission bits prevent stat/read of it or the comparison target (e.g. root or a parent is unreadable), during _reject_degenerate_root checks.

Common situations: DEEPAGENTS_HOME under a root-owned directory with mode 700 while running as a normal user; restricted mount points in containers; NFS/ACL setups where stat is denied.

Understand the failure class

Background: Permission denied / not authorized / 403 Forbidden: access-control rejections when the caller lacks the required role, grant, or ownership — this error's family across 18 libraries.

Related errors


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