{"record":{"id":"4024635ab24b26e5","repo":"abhigyanpatwari/GitNexus","slug":"label-target-has-a-non-directory-or-symlink-pare","errorCode":null,"errorMessage":"{label} target has a non-directory or symlink parent: {relative}","messagePattern":"(.+?) target has a non-directory or symlink parent: (.+?)","errorType":"exception","errorClass":"SandboxError","httpStatus":null,"severity":"error","filePath":"eval/workflow_bench/proposer_sandbox.py","lineNumber":602,"sourceCode":"\n    This runs before Bubblewrap, so ordinary ``Path.mkdir``/``touch`` calls\n    are not acceptable: an untrusted tracked parent symlink could redirect a\n    mount placeholder write into the host filesystem.\n    \"\"\"\n\n    flags = os.O_RDONLY | os.O_DIRECTORY | getattr(os, \"O_CLOEXEC\", 0)\n    nofollow = getattr(os, \"O_NOFOLLOW\", 0)\n    current_fd = os.open(clone, flags | nofollow)\n    try:\n        for part in relative.parts[:-1]:\n            try:\n                os.mkdir(part, mode=0o700, dir_fd=current_fd)\n            except FileExistsError:\n                pass\n            try:\n                next_fd = os.open(part, flags | nofollow, dir_fd=current_fd)\n            except OSError as exc:\n                raise SandboxError(f\"{label} target has a non-directory or symlink parent: {relative}\") from exc\n            os.close(current_fd)\n            current_fd = next_fd\n\n        leaf = relative.parts[-1]\n        try:\n            mode = os.stat(leaf, dir_fd=current_fd, follow_symlinks=False).st_mode\n        except FileNotFoundError:\n            mode = None\n        if mode is not None and stat.S_ISLNK(mode):\n            raise SandboxError(f\"{label} target cannot be a symlink: {relative}\")\n        if directory is True:\n            if mode is None:\n                os.mkdir(leaf, mode=0o700, dir_fd=current_fd)\n            elif not stat.S_ISDIR(mode):\n                raise SandboxError(f\"{label} directory target has the wrong type: {relative}\")\n        elif directory is False:\n            if mode is None:\n                file_flags = os.O_WRONLY | os.O_CREAT | os.O_EXCL | nofollow","sourceCodeStart":584,"sourceCodeEnd":620,"githubUrl":"https://github.com/abhigyanpatwari/GitNexus/blob/d540b00184d71a896261ee02670da9a92d59d8f7/eval/workflow_bench/proposer_sandbox.py#L584-L620","documentation":"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.","triggerScenarios":"_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.","commonSituations":"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.","solutions":["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.","If a tracked symlink is the cause, remove or restructure it in the repo so the parent chain is real directories.","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.","Run with a freshly built clone/worktree to rule out leftover state."],"exampleFix":"// before — intermediate component 'lib' is a committed symlink\ntarget: src/lib/node_modules\n// after — restructure so every parent is a real directory\ntarget: src/vendor/node_modules","handlingStrategy":"validation","validationCode":"from pathlib import Path, PurePosixPath\nimport os, stat\n\ndef parents_are_real_dirs(clone: Path, relative: str) -> bool:\n    fd = os.open(clone, os.O_RDONLY | os.O_DIRECTORY | os.O_NOFOLLOW)\n    try:\n        for part in PurePosixPath(relative).parts[:-1]:\n            try:\n                nxt = os.open(part, os.O_RDONLY | os.O_DIRECTORY | os.O_NOFOLLOW, dir_fd=fd)\n            except OSError:\n                return False\n            os.close(fd); fd = nxt\n    finally:\n        os.close(fd)\n    return True","typeGuard":null,"tryCatchPattern":"from .proposer_sandbox import SandboxError\n\ntry:\n    target = _prepare_clone_target(clone, PurePosixPath(rel), directory=want_dir, label='asset')\nexcept SandboxError:\n    # a parent component is a symlink or non-directory; abort this arm\n    raise","preventionTips":["Never commit symlinks along harness-managed target paths.","Use a freshly created detached worktree per arm.","Audit task/dependency target paths so every parent component is a real directory."],"tags":["sandbox","symlink-guard","trust-boundary","task-assets","path-validation"],"backgroundTag":null,"analyzedSha":"d540b00184d71a896261ee02670da9a92d59d8f7","analyzedAt":"2026-08-12T19:50:25.132Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}