abhigyanpatwari/GitNexus · error · SandboxError

{label} target has a non-directory or symlink parent: {relat

Error message

{label} target has a non-directory or symlink parent: {relative}

What it means

Raised by _prepare_clone_target while walking the intermediate (non-leaf) components of a clone-local target path using os.open with O_NOFOLLOW | O_DIRECTORY. If opening an intermediate component fails, that component is either a symlink (blocked by O_NOFOLLOW) or not a directory (blocked by O_DIRECTORY). The check exists because this runs before bubblewrap: an untrusted tracked parent symlink could otherwise redirect a mount-placeholder write into the host filesystem.

Source

Thrown at eval/workflow_bench/proposer_sandbox.py:602

    This runs before Bubblewrap, so ordinary ``Path.mkdir``/``touch`` calls
    are not acceptable: an untrusted tracked parent symlink could redirect a
    mount placeholder write into the host filesystem.
    """

    flags = os.O_RDONLY | os.O_DIRECTORY | getattr(os, "O_CLOEXEC", 0)
    nofollow = getattr(os, "O_NOFOLLOW", 0)
    current_fd = os.open(clone, flags | nofollow)
    try:
        for part in relative.parts[:-1]:
            try:
                os.mkdir(part, mode=0o700, dir_fd=current_fd)
            except FileExistsError:
                pass
            try:
                next_fd = os.open(part, flags | nofollow, dir_fd=current_fd)
            except OSError as exc:
                raise SandboxError(f"{label} target has a non-directory or symlink parent: {relative}") from exc
            os.close(current_fd)
            current_fd = next_fd

        leaf = relative.parts[-1]
        try:
            mode = os.stat(leaf, dir_fd=current_fd, follow_symlinks=False).st_mode
        except FileNotFoundError:
            mode = None
        if mode is not None and stat.S_ISLNK(mode):
            raise SandboxError(f"{label} target cannot be a symlink: {relative}")
        if directory is True:
            if mode is None:
                os.mkdir(leaf, mode=0o700, dir_fd=current_fd)
            elif not stat.S_ISDIR(mode):
                raise SandboxError(f"{label} directory target has the wrong type: {relative}")
        elif directory is False:
            if mode is None:
                file_flags = os.O_WRONLY | os.O_CREAT | os.O_EXCL | nofollow

View on GitHub (pinned to d540b00184)

Solutions

  1. Inspect each intermediate component of the path in the error inside the worktree: `ls -la <worktree>/<each parent part>` and look for a symlink or non-directory entry.
  2. If a tracked symlink is the cause, remove or restructure it in the repo so the parent chain is real directories.
  3. If the collision is from a previous run, ensure the worktree is freshly created (make_worktree produces a clean detached worktree) and that no setup step wrote a file across the target's parent path.
  4. Run with a freshly built clone/worktree to rule out leftover state.

Example fix

// before — intermediate component 'lib' is a committed symlink
target: src/lib/node_modules
// after — restructure so every parent is a real directory
target: src/vendor/node_modules
Defensive patterns

Strategy: validation

Validate before calling

from pathlib import Path, PurePosixPath
import os, stat

def parents_are_real_dirs(clone: Path, relative: str) -> bool:
    fd = os.open(clone, os.O_RDONLY | os.O_DIRECTORY | os.O_NOFOLLOW)
    try:
        for part in PurePosixPath(relative).parts[:-1]:
            try:
                nxt = os.open(part, os.O_RDONLY | os.O_DIRECTORY | os.O_NOFOLLOW, dir_fd=fd)
            except OSError:
                return False
            os.close(fd); fd = nxt
    finally:
        os.close(fd)
    return True

Try / catch

from .proposer_sandbox import SandboxError

try:
    target = _prepare_clone_target(clone, PurePosixPath(rel), directory=want_dir, label='asset')
except SandboxError:
    # a parent component is a symlink or non-directory; abort this arm
    raise

Prevention

When it happens

Trigger: _prepare_clone_target(clone, relative, directory=..., label=...) is asked to create/validate a target inside the clone, but one of relative.parts[:-1] (an intermediate directory) is a symlink or a regular file / special node rather than a directory, so `os.open(part, O_RDONLY|O_DIRECTORY|O_NOFOLLOW, dir_fd=current_fd)` raises OSError.

Common situations: A task or dependency-snapshot path traverses a tracked symlink that was committed into the repo (e.g. `lib/foo` is a symlink); a stale file from a previous run collides with a directory component of the new target; the clone was populated by a tool that created intermediate files where directories were expected.

Related errors


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