abhigyanpatwari/GitNexus · error · SandboxError

sandbox_copy must be a repository-relative path: {raw!r}

Error message

sandbox_copy must be a repository-relative path: {raw!r}

What it means

Raised by _sandbox_copy_declarations for each declared path string that is absolute (starts with '/'), has no parts (empty after PurePosixPath parsing), or contains a '..' component anywhere in its parts. The harness only accepts repository-relative paths that stay within the repo root — this is both a path-traversal security guard and a determinism guard (absolute or escaping paths would capture bytes outside the declared repo identity).

Source

Thrown at eval/workflow_bench/task_assets.py:551

        self._record_directory(relative)

    def finished_entries(self) -> tuple[AssetManifestEntry, ...]:
        return tuple(sorted(self.entries.values(), key=lambda entry: entry.path.as_posix()))


def _sandbox_copy_declarations(
    task: Mapping[str, Any],
) -> tuple[tuple[str, ...], tuple[PurePosixPath, ...]]:
    raw_declarations = task.get("sandbox_copy", [])
    if not isinstance(raw_declarations, list) or not all(isinstance(item, str) and item for item in raw_declarations):
        raise SandboxError("sandbox_copy must be a list of nonblank repository-relative paths")
    declarations = tuple(raw_declarations)
    paths: list[PurePosixPath] = []
    for raw in declarations:
        relative = PurePosixPath(raw)
        if relative.is_absolute() or not relative.parts or ".." in relative.parts:
            raise SandboxError(f"sandbox_copy must be a repository-relative path: {raw!r}")
        _validate_manifest_path(relative)
        paths.append(relative)
    for index, path in enumerate(paths):
        for other in paths[index + 1 :]:
            if path == other or path in other.parents or other in path.parents:
                raise SandboxError(f"sandbox_copy declarations overlap: {path} and {other}")
    return declarations, tuple(paths)


def _sandbox_dependency_declarations(
    task: Mapping[str, Any],
) -> tuple[_DependencyDeclaration, ...]:
    raw_declarations = task.get("sandbox_dependencies", [])
    if not isinstance(raw_declarations, list):
        raise SandboxError("sandbox_dependencies must be a list")
    declarations: list[_DependencyDeclaration] = []
    for item in raw_declarations:
        if (

View on GitHub (pinned to d540b00184)

Solutions

  1. Rewrite each sandbox_copy entry as a clean repo-relative path with no '..': `src/lib`, `tests/fixtures`, not `/home/user/repo/src` or `../shared/lib`.
  2. Normalize at authoring time with `os.path.relpath(path, repo_root)` and reject results starting with '..'.
  3. Validate with the provided path guard before calling prepare() (see typeGuard).
  4. If a path outside the repo is genuinely needed, copy it into the repo first or declare it as a sandbox_dependency with a proper source.

Example fix

// before
{"sandbox_copy": ["/home/user/repo/src", "../shared/include"]}

// after
{"sandbox_copy": ["src", "include"]}
Defensive patterns

Strategy: type-guard

Validate before calling

from pathlib import PurePosixPath

def validate_copy_paths_relative(task: dict) -> None:
    for raw in task.get("sandbox_copy", []):
        p = PurePosixPath(raw)
        if p.is_absolute() or not p.parts or ".." in p.parts:
            raise ValueError(f"sandbox_copy must be repository-relative, got {raw!r}")

validate_copy_paths_relative(task)

Type guard

from pathlib import PurePosixPath

def is_repo_relative_bounded(raw: str) -> bool:
    p = PurePosixPath(raw)
    return not p.is_absolute() and bool(p.parts) and ".." not in p.parts

Try / catch

from eval.workflow_bench.propposer_sandbox import SandboxError

try:
    snapshot = cache.prepare(task, repo=repo, resolved_sha=sha)
except SandboxError as exc:
    if "repository-relative path" in str(exc):
        # rewrite the offending path to a clean repo-relative form
        raise
    raise

Prevention

When it happens

Trigger: A declaration like `/abs/path`, `../sibling`, `repo/../outside`, `./` (no parts), or `''` that produced an empty PurePosixPath. Also a Windows-style path with a drive letter parsed unexpectedly. The check is per-element: `relative.is_absolute() or not relative.parts or '..' in relative.parts`.

Common situations: Author copies an absolute path from an IDE. Relative path that walks up with `..`. A templating bug that prefixes '/'. A path normalization step that collapses `a/../..` to an escape.

Related errors


AI-assisted analysis of abhigyanpatwari/GitNexus@d540b00184 (2026-08-12). Data as JSON: /api/errors/4702f1a8471fee6f. Report an issue: GitHub.