{"record":{"id":"f704edf9b2e66717","repo":"agentscope-ai/agentscope","slug":"label-must-be-a-real-directory-path","errorCode":null,"errorMessage":"{label} must be a real directory: {path}","messagePattern":"(.+?) must be a real directory: (.+?)","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"src/agentscope/workspace/_bubblewrap/_bubblewrap_backend.py","lineNumber":507,"sourceCode":"            )\n        except ValueError as exc:\n            raise RuntimeError(\n                \"host_cache_dir was removed or replaced before execution.\",\n            ) from exc\n        if identity != self._host_cache_identity:\n            raise RuntimeError(\n                \"host_cache_dir was replaced before execution.\",\n            )\n\n    @staticmethod\n    def _directory_identity(\n        path: str,\n        *,\n        label: str,\n    ) -> tuple[int, int]:\n        \"\"\"Return a stable identity for a real directory mount source.\"\"\"\n        if os.path.islink(path) or not os.path.isdir(path):\n            raise ValueError(f\"{label} must be a real directory: {path}\")\n        stat_result = os.stat(path, follow_symlinks=False)\n        return stat_result.st_dev, stat_result.st_ino\n\n    @staticmethod\n    def _paths_overlap(left: str, right: str) -> bool:\n        \"\"\"Return whether either real path contains the other.\"\"\"\n        left_real = os.path.realpath(left)\n        right_real = os.path.realpath(right)\n        try:\n            common = os.path.commonpath([left_real, right_real])\n        except ValueError:\n            return False\n        return common in (left_real, right_real)\n\n    def _base_env(self) -> dict[str, str]:\n        \"\"\"Environment visible to sandboxed commands.\"\"\"\n        path_parts = [\n            f\"{SANDBOX_WORKDIR}/.agentscope/.venv/bin\",","sourceCodeStart":489,"sourceCodeEnd":525,"githubUrl":"https://github.com/agentscope-ai/agentscope/blob/e90f1c7592896cc95f6e5ee506194f533378247d/src/agentscope/workspace/_bubblewrap/_bubblewrap_backend.py#L489-L525","documentation":"_directory_identity requires every mount source to be a real directory (not a symlink, not a file). It raises ValueError with the offending label and path; __init__ and the pre-execution validator both call it, so the error surfaces either at construction or at first run.","triggerScenarios":"Passing host_workdir/host_tmpdir/host_cache_dir that is a symlink to a directory, a regular file, or a nonexistent path.","commonSituations":"Symlinked workspace dirs (common in dotfile-managed setups or Docker volumes), typos in paths, or pointing at a path created later by another service.","solutions":["Replace the symlink with a real directory (or bind mount) and pass the real path","Create the directory first: os.makedirs(path, exist_ok=True)","Verify with os.path.islink(path) == False and os.path.isdir(path) before constructing"],"exampleFix":"# before\nws = BubblewrapWorkspace(host_workdir='/data/link-to-ws')  # symlink\n# after\nos.makedirs('/data/ws', exist_ok=True)\nws = BubblewrapWorkspace(host_workdir='/data/ws')","handlingStrategy":"type-guard","validationCode":"import os\nfor p in (workdir, tmpdir, cache_dir):\n    if p is not None:\n        assert not os.path.islink(p) and os.path.isdir(p), f'bad mount source: {p}'","typeGuard":"def is_real_dir(p: str) -> bool:\n    return not os.path.islink(p) and os.path.isdir(p)","tryCatchPattern":"except ValueError as e:\n    if 'must be a real directory' in str(e):\n        os.makedirs(p, exist_ok=True)  # then retry construction","preventionTips":["Replace workspace symlinks with real directories or bind mounts","Create all mount dirs before constructing the backend"],"tags":["bubblewrap","symlink","filesystem","mount"],"backgroundTag":"mount-source-not-real-directory","analyzedSha":"e90f1c7592896cc95f6e5ee506194f533378247d","analyzedAt":"2026-08-28T18:24:12.087Z","schemaVersion":2},"datasetVersion":"2026-08-28T21:17:43.275Z"}