abhigyanpatwari/GitNexus · error · SandboxError

sandbox_copy parent must be a directory: {relative}

Error message

sandbox_copy parent must be a directory: {relative}

What it means

Raised by _open_child when require_directory=True (set for every intermediate component by _open_relative) and os.stat shows the component is not a directory. This catches a regular file sitting where the walker needs to descend.

Source

Thrown at eval/workflow_bench/task_assets.py:632

        os.close(current)
        raise


def _open_child(
    parent_descriptor: int,
    name: str,
    relative: PurePosixPath,
    *,
    require_directory: bool = False,
) -> int:
    try:
        metadata = os.stat(name, dir_fd=parent_descriptor, follow_symlinks=False)
    except OSError as exc:
        raise SandboxError(f"sandbox_copy path is unavailable: {relative}: {exc}") from exc
    if stat.S_ISLNK(metadata.st_mode):
        raise SandboxError(f"sandbox_copy must not traverse a symlink: {relative}")
    if require_directory and not stat.S_ISDIR(metadata.st_mode):
        raise SandboxError(f"sandbox_copy parent must be a directory: {relative}")
    if not (stat.S_ISDIR(metadata.st_mode) or stat.S_ISREG(metadata.st_mode)):
        raise SandboxError(f"sandbox_copy accepts only regular files and directories: {relative}")
    flags = os.O_RDONLY | getattr(os, "O_CLOEXEC", 0) | getattr(os, "O_NOFOLLOW", 0)
    if stat.S_ISDIR(metadata.st_mode):
        flags |= os.O_DIRECTORY
    else:
        flags |= getattr(os, "O_NONBLOCK", 0)
    try:
        descriptor = os.open(name, flags, dir_fd=parent_descriptor)
    except OSError as exc:
        raise SandboxError(f"sandbox_copy path changed or is unreadable: {relative}: {exc}") from exc
    opened = os.fstat(descriptor)
    if not (stat.S_ISDIR(opened.st_mode) or stat.S_ISREG(opened.st_mode)):
        os.close(descriptor)
        raise SandboxError(f"sandbox_copy accepts only regular files and directories: {relative}")
    if (
        opened.st_dev,
        opened.st_ino,

View on GitHub (pinned to d540b00184)

Solutions

  1. Confirm each intermediate component is a directory at the SHA: `git cat-file -e <sha>:<parent>/`
  2. Correct the declaration path so only the final component is a file
  3. Re-pin the task to a SHA whose layout matches the declaration
Defensive patterns

Strategy: validation

Validate before calling

import os
from pathlib import Path

def parents_are_dirs(root: Path, declared: list[str]) -> list[str]:
    bad = []
    for decl in declared:
        parts = Path(decl).parts[:-1]  # intermediate components only
        walker = root
        for part in parts:
            walker = walker / part
            try:
                if not walker.is_dir():
                    bad.append(f"{decl}: {walker} is not a directory"); break
            except OSError as exc:
                bad.append(f"{decl}: {exc}"); break
    return bad

Try / catch

from eval.workflow_bench.proposer_sandbox import SandboxError

try:
    snapshot = cache.prepare(task, repo=repo, resolved_sha=sha)
except SandboxError as exc:
    if "parent must be a directory" in str(exc):
        raise SystemExit(f"declaration path layout invalid: {exc}") from exc
    raise

Prevention

When it happens

Trigger: A declaration path like src/index.ts/utils where src/index.ts is a file; an intermediate name collides with an existing regular file; a directory was renamed to a file between pins.

Common situations: Renaming a directory to a file (or vice versa) between commit pins; typo'd declaration path that joins a filename with further children.

Related errors


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