OpenBMB/ChatDev · error · ValueError

script path is outside workspace root

Error message

script path is outside workspace root

What it means

WorkspaceCommandContext.resolve_under_workspace confines all paths to the workspace root: it resolves the candidate absolutely and rejects it unless it equals the root or is a descendant of it. This is a sandbox guarantee so uv-run scripts cannot escape the workspace via ../ traversal or absolute paths. Note resolve() also collapses symlinks, so symlinked locations resolving outside the root are rejected too.

Source

Thrown at functions/function_calling/uv_related.py:60

        if ctx is None:
            raise ValueError("_context is required for uv tools")
        self.workspace_root = self._require_workspace(ctx.get("python_workspace_root"))
        self._raw_ctx = ctx

    @staticmethod
    def _require_workspace(raw_path: Any) -> Path:
        if raw_path is None:
            raise ValueError("python_workspace_root missing from _context")
        path = Path(raw_path).expanduser().resolve()
        path.mkdir(parents=True, exist_ok=True)
        return path

    def resolve_under_workspace(self, relative_path: str | Path) -> Path:
        candidate = Path(relative_path)
        absolute = candidate if candidate.is_absolute() else self.workspace_root / candidate
        absolute = absolute.expanduser().resolve()
        if self.workspace_root not in absolute.parents and absolute != self.workspace_root:
            raise ValueError("script path is outside workspace root")
        return absolute


def _validate_packages(packages: Sequence[str]) -> List[str]:
    normalized: List[str] = []
    for pkg in packages:
        if not isinstance(pkg, str):
            raise ValueError("package entries must be strings")
        stripped = pkg.strip()
        if not stripped:
            raise ValueError("package names cannot be empty")
        if not _SAFE_PACKAGE_RE.match(stripped):
            raise ValueError(f"unsafe characters detected in package spec {pkg}")
        if stripped.startswith("-"):
            raise ValueError(f"flags are not allowed in packages list: {pkg}")
        normalized.append(stripped)
    if not normalized:
        raise ValueError("at least one package is required")

View on GitHub (pinned to 4fb2db0ea9)

Solutions

  1. Move or copy the script under the workspace root and reference it relatively
  2. Replace symlinks that resolve outside the workspace with real files/directories inside it
  3. Normalize and reject '..' segments in user-supplied paths before building the final path

Example fix

# before
uv_run(script="../shared/run.py")
# after
uv_run(script="shared/run.py")  # shared/ moved/copied under workspace root
Defensive patterns

Strategy: validation

Validate before calling

from pathlib import Path

def under_root(root, p) -> bool:
    root = Path(root).resolve()
    q = (root / p if not Path(p).is_absolute() else Path(p)).resolve()
    return q == root or root in q.parents

if not under_root(ws_root, script):
    script = copy_into_workspace(ws_root, script)

Type guard

def is_within_workspace(root, candidate) -> bool:
    root = Path(root).expanduser().resolve()
    c = Path(candidate)
    c = c if c.is_absolute() else root / c
    c = c.expanduser().resolve()
    return c == root or root in c.parents

Try / catch

try:
    uv_run(script=script, _context=ctx)
except ValueError as e:
    if "outside workspace root" in str(e):
        raise ValueError(f"relocate {script} inside the workspace") from e
    raise

Prevention

When it happens

Trigger: Passing a script path like "../outside/run.py" or "/etc/run.py" to uv_run; a path inside the workspace that is a symlink to a directory outside it; path built by joining user input that contains .. segments.

Common situations: Monorepo setups where the script lives in a sibling directory; symlinked workspaces (e.g. /tmp symlink on macOS) where resolve() escapes the root; naive path joins of untrusted user input.

Related errors


AI-assisted analysis of OpenBMB/ChatDev@4fb2db0ea9 (2026-08-27). Data as JSON: /api/errors/0061886c459e1def. Report an issue: GitHub.