{"record":{"id":"a12b0e20f28c4a21","repo":"langchain-ai/deepagents","slug":"path-is-not-a-directory-path","errorCode":null,"errorMessage":"Path is not a directory: {path}","messagePattern":"Path is not a directory: (.+?)","errorType":"exception","errorClass":"OSError","httpStatus":null,"severity":"error","filePath":"libs/code/deepagents_code/offload.py","lineNumber":101,"sourceCode":"    \"\"\"Create `path` if needed and restrict it to the current user.\n\n    Only ever call this on directories owned by this process's storage (a temp\n    dir or a dedicated subdirectory), never on the shared `~/.deepagents` config\n    root.\n\n    Args:\n        path: Directory to create and harden to `0o700`.\n\n    Raises:\n        OSError: If the path exists but is not a directory, or the directory\n            cannot be created or its mode changed (e.g. a read-only mount).\n        PermissionError: If the existing directory is owned by another local user.\n    \"\"\"\n    path.mkdir(mode=0o700, parents=True, exist_ok=True)\n    info = path.lstat()\n    if not stat.S_ISDIR(info.st_mode):\n        msg = f\"Path is not a directory: {path}\"\n        raise OSError(msg)\n    getuid = getattr(os, \"getuid\", None)\n    if getuid is not None and info.st_uid != getuid():\n        msg = f\"Directory is owned by another user: {path}\"\n        raise PermissionError(msg)\n    # `mkdir(mode=...)` does not tighten an existing directory. These directories\n    # can hold conversation data and offloaded tool results, so they must remain\n    # inaccessible to other local accounts regardless of the process umask.\n    path.chmod(0o700)\n\n\ndef _probe_writable(path: Path) -> None:\n    \"\"\"Confirm `path` accepts new files (catches read-only mounts).\n\n    Creating the directory is insufficient when it already exists on a read-only\n    mount; a temporary file proves writes can succeed.\n\n    Args:\n        path: Directory to probe.","sourceCodeStart":83,"sourceCodeEnd":119,"githubUrl":"https://github.com/langchain-ai/deepagents/blob/a1af029e6e73cb17c36bff823d227747b28e91e1/libs/code/deepagents_code/offload.py#L83-L119","documentation":"`_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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"# before (file blocks the dir)\nls -la ~/.deepagents/artifacts  # -rw-r--r-- file\n# after\nrm ~/.deepagents/artifacts && mkdir -p ~/.deepagents/artifacts","handlingStrategy":"validation","validationCode":"p = Path(path)\nif p.exists() and not p.is_dir():\n    raise RuntimeError(f\"Expected a directory at {p}, found {p.stat().st_size}-byte file; remove it first\")","typeGuard":"def is_dir(p: Path) -> bool:\n    return p.exists() and p.is_dir()","tryCatchPattern":"try:\n    root = _artifacts_root()\nexcept OSError as e:\n    logging.error(\"state dir unusable: %s\", e)\n    raise SystemExit(1)","preventionTips":["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"],"tags":["filesystem","permissions","offload","oserror"],"backgroundTag":"path-is-not-a-directory","analyzedSha":"a1af029e6e73cb17c36bff823d227747b28e91e1","analyzedAt":"2026-08-29T11:43:24.718Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}