abhigyanpatwari/GitNexus · critical · SandboxError
sandbox_copy path changed or is unreadable: {relative}: {exc
Error message
sandbox_copy path changed or is unreadable: {relative}: {exc} What it means
TOCTOU guard in _open_child: os.stat succeeded but the subsequent os.open(name, flags, dir_fd=<parent>) raised OSError. The chained exc is typically ENOENT (file deleted between stat and open), EACCES (permissions changed), or ENFILE/EMFILE (fd exhaustion).
Source
Thrown at eval/workflow_bench/task_assets.py:643
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,
stat.S_IFMT(opened.st_mode),
) != (
metadata.st_dev,
metadata.st_ino,
stat.S_IFMT(metadata.st_mode),
):
os.close(descriptor)
raise SandboxError(f"sandbox_copy path changed while opening: {relative}")
return descriptor
View on GitHub (pinned to d540b00184)
Solutions
- Ensure no other process mutates the repo during snapshot capture (one worktree per concurrent arm)
- Raise the fd soft limit: `ulimit -n 1048576`
- Re-run capture; a transient race is the most common cause
- If persistent, audit concurrent writers with lsof or fatrace
Defensive patterns
Strategy: try-catch
Validate before calling
import resource # raise the fd soft limit before any snapshot capture soft, hard = resource.getrlimit(resource.RLIMIT_NOFILE) resource.setrlimit(resource.RLIMIT_NOFILE, (min(hard, 1<<20), hard))
Try / catch
from eval.workflow_bench.proposer_sandbox import SandboxError
attempts = 0
while True:
try:
snapshot = cache.prepare(task, repo=repo, resolved_sha=sha); break
except SandboxError as exc:
if "path changed or is unreadable" in str(exc) and attempts < 2:
attempts += 1 # transient TOCTOU race; retry with backoff
continue
raise Prevention
- Raise the file-descriptor soft limit before capture (`ulimit -n 1048576`)
- Use one private worktree per concurrent arm so no cross-arm mutation occurs
- Quiesce background indexers and scanners before capture
When it happens
Trigger: Another process mutates the worktree during snapshot capture; concurrent git checkout; antivirus/scanner holding or deleting files; the process hit its file-descriptor limit mid-walk.
Common situations: Two benchmark arms against the same worktree simultaneously; background indexers rewriting the tree; very deep trees exhausting the fd soft limit.
Related errors
- transcript artifact changed while opening: {path}
- transcript artifact changed while reading: {path}
- sandbox_copy path changed while opening: {relative}
- {label} is unreadable: {path}: {exc}
- {label} changed while opening: {path}
AI-assisted analysis of abhigyanpatwari/GitNexus@d540b00184 (2026-08-12).
Data as JSON: /api/errors/cc48ea38906393b7.
Report an issue: GitHub.