abhigyanpatwari/GitNexus · critical · ValueError
candidate overlay cannot traverse symlinks: {overlay}
Error message
candidate overlay cannot traverse symlinks: {overlay} What it means
Thrown by candidate_overlay_files (evolution.py:279) when the resolved overlay path differs from the lexical absolute path. The harness requires the overlay root to be a real path that does not traverse any symlink, because resolve() following a symlink would hide a redirect outside the trusted tree. Any symlink component in the supplied path triggers this.
Source
Thrown at eval/workflow_bench/evolution.py:279
env=build_sandbox_environment(),
)
return command, result
def candidate_overlay_files(overlay: Path) -> list[Path]:
"""Return a candidate's files after enforcing the benchmark trust boundary.
Candidates may change only the canonical repo-local skill prompts. They
cannot modify task code, tests, or verification commands and thereby game
the promotion gate.
"""
overlay = overlay.expanduser().absolute()
try:
resolved_overlay = overlay.resolve(strict=True)
except OSError as exc:
raise ValueError(f"candidate overlay is not a directory: {overlay}") from exc
if resolved_overlay != overlay:
raise ValueError(f"candidate overlay cannot traverse symlinks: {overlay}")
_require_real_directory(overlay, label="candidate overlay")
entries: list[Path] = []
pending = [overlay]
entry_count = 0
while pending:
directory = pending.pop()
child_directories: list[Path] = []
try:
iterator = os.scandir(directory)
except OSError as exc:
raise ValueError(f"candidate overlay directory is unreadable: {directory}: {exc}") from exc
with iterator:
for item in iterator:
entry_count += 1
if entry_count > MAX_CANDIDATE_ENTRIES:
raise ValueError(f"candidate overlay exceeds the {MAX_CANDIDATE_ENTRIES}-entry limit")
path = Path(item.path)View on GitHub (pinned to d540b00184)
Solutions
- Pass a real (non-symlink) absolute path; resolve the symlink yourself and pass the resolved real path if it is itself real.
- Copy the overlay into a fresh real directory (shutil.copytree into tempfile.mkdtemp) and pass that.
- On macOS use /private/tmp/... directly instead of /tmp/...; avoid symlinked project roots.
Example fix
# before: overlay root is a symlink (e.g. macOS /tmp)
apply_candidate_overlay(Path('/tmp/overlay'), ...) # /tmp -> /private/tmp
# after: copy to a real path with no symlink components
import tempfile, shutil
real = Path(tempfile.mkdtemp(prefix='wfbench-overlay-'))
shutil.copytree('/tmp/overlay', real / 'overlay')
assert (real / 'overlay').resolve() == (real / 'overlay') # no symlink hop
apply_candidate_overlay(real / 'overlay', ...) Defensive patterns
Strategy: validation
Validate before calling
from pathlib import Path
def overlay_has_no_symlink_hop(p: Path) -> bool:
p = p.expanduser().absolute()
try:
return p.resolve(strict=True) == p
except OSError:
return False Type guard
null
Try / catch
try:
apply_candidate_overlay(overlay, worktree, sandbox=sandbox)
except ValueError as exc:
if 'cannot traverse symlinks' in str(exc):
# copy overlay to a real path (no symlink components) and retry
... Prevention
- Pass a real absolute path with no symlink components.
- On macOS use /private/tmp not /tmp; avoid symlinked project roots.
- Snapshot the overlay into tempfile.mkdtemp() before benchmarking.
When it happens
Trigger: The overlay path contains a symlink component (the root itself is a symlink, or a parent like /tmp is a symlink). resolve() lands on a different path than the lexical absolute input, so the equality check fails.
Common situations: On macOS /tmp -> /private/tmp and /var -> /private/var; pointing at a symlinked project checkout; a CI workspace that symlinks into another volume.
Related errors
- candidate destination must be a regular non-symlink file: {r
- candidate overlay cannot contain symlinks: {relative}
- candidate overlay entries must be regular files: {relative}
- overlay destination parent must not be a symlink: {target}
- Refusing to delete ${dbPath}: resolved path ${realPath} is o
AI-assisted analysis of abhigyanpatwari/GitNexus@d540b00184 (2026-08-12).
Data as JSON: /api/errors/07a7f1e1a884c32b.
Report an issue: GitHub.