{"record":{"id":"601b9353f0908158","repo":"abhigyanpatwari/GitNexus","slug":"label-must-be-a-real-non-symlink-directory-pat","errorCode":null,"errorMessage":"{label} must be a real non-symlink directory: {path}","messagePattern":"(.+?) must be a real non-symlink directory: (.+?)","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"eval/workflow_bench/evolution.py","lineNumber":71,"sourceCode":"    \"subagents), or sum usage from the digest-bound transcript_artifacts in \"\n    \"each run output, deduplicating events \"\n    \"that share one message.id.\"\n)\nEVIDENCE_MAX_AGE_DAYS = 90\nMAX_CANDIDATE_OVERLAY_BYTES = 4 * 1024 * 1024\nMAX_SKILL_FINGERPRINT_BYTES = 4 * 1024 * 1024\nMAX_CANDIDATE_ENTRIES = 256\nMAX_CANDIDATE_FILES = 64\nMAX_CANDIDATE_PATH_BYTES = 512\n\n\ndef _require_real_directory(path: Path, *, label: str) -> None:\n    try:\n        metadata = path.lstat()\n    except OSError as exc:\n        raise ValueError(f\"{label} is unavailable: {path}: {exc}\") from exc\n    if stat.S_ISLNK(metadata.st_mode) or not stat.S_ISDIR(metadata.st_mode):\n        raise ValueError(f\"{label} must be a real non-symlink directory: {path}\")\n\n\ndef _require_directory_chain(root: Path, relative: Path, *, label: str) -> None:\n    \"\"\"Validate each lexical directory without erasing links via resolve().\"\"\"\n\n    _require_real_directory(root, label=label)\n    current = root\n    for part in relative.parts:\n        if part in {\"\", \".\", \"..\"}:\n            raise ValueError(f\"{label} contains an unsafe path component: {relative}\")\n        current /= part\n        _require_real_directory(current, label=label)\n\n\ndef _bounded_regular_bytes(path: Path, *, limit: int, label: str) -> bytes:\n    \"\"\"Read one bounded regular file without following its leaf link.\"\"\"\n\n    try:","sourceCodeStart":53,"sourceCodeEnd":89,"githubUrl":"https://github.com/abhigyanpatwari/GitNexus/blob/d540b00184d71a896261ee02670da9a92d59d8f7/eval/workflow_bench/evolution.py#L53-L89","documentation":"Thrown by _require_real_directory() in eval/workflow_bench/evolution.py when lstat() succeeds but the entry is a symlink (S_ISLNK) or not a directory (not S_ISDIR). The skill/promotion pipeline refuses symlinks and non-directories because they could redirect outside the sandbox, and refuses to follow links (no resolve()) to avoid erasing link semantics.","triggerScenarios":"An overlay or skill-directory path that is actually a symlink (e.g. `ln -s /elsewhere /tmp/overlay`), or a path that points at a regular file rather than a directory. _require_directory_chain calls this for the root and for each lexical path component.","commonSituations":"User created a convenience symlink to their candidate overlay; a tool packed the overlay as a tarball that extracted symlinks; path points at a file (e.g. a yaml) instead of its parent dir; CI set up a symlinked workspace.","solutions":["Replace the symlink with the real directory: `cp -rL` to dereference, or point directly at the target directory.","Re-extract any tarball with --no-same-owner and dereference, or filter symlinks out of the candidate set.","Point the argument at the directory itself, not a symlink or a file inside it.","Audit the candidate-generation step to ensure it writes real directories."],"exampleFix":"# before\nln -s /home/user/real-overlay /tmp/overlay\ncandidate_overlay_payload(Path('/tmp/overlay'))\n# ValueError: candidate overlay directory must be a real non-symlink directory\n\n# after\ncp -rL /home/user/real-overlay /tmp/overlay\ncandidate_overlay_payload(Path('/tmp/overlay'))","handlingStrategy":"validation","validationCode":"import os, stat\ndef ensure_real_dir(p: Path, label: str) -> None:\n    st = p.lstat()\n    if stat.S_ISLNK(st.st_mode) or not stat.S_ISDIR(st.st_mode):\n        raise SystemExit(f'{label} must be a real directory, not a symlink/file: {p}')\n# call before _require_real_directory","typeGuard":"def is_not_real_directory_error(exc: ValueError) -> bool:\n    return 'must be a real non-symlink directory' in str(exc)","tryCatchPattern":"try:\n    candidate_overlay_payload(overlay)\nexcept ValueError as e:\n    if is_not_real_directory_error(e):\n        # re-stage with cp -rL to dereference, then retry\n        raise\n    raise","preventionTips":["Never pass a symlink as the overlay root; dereference with cp -rL first.","Ensure tarball extraction does not preserve symlinks.","Point the argument at a directory, never at a file."],"tags":["workflow-bench","validation","symlink","path","sandbox","security","evolution"],"backgroundTag":null,"analyzedSha":"d540b00184d71a896261ee02670da9a92d59d8f7","analyzedAt":"2026-08-12T19:50:25.132Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}