abhigyanpatwari/GitNexus · error · SandboxError
sandbox_copy directory is unreadable: {relative}: {exc}
Error message
sandbox_copy directory is unreadable: {relative}: {exc} What it means
While snapshotting, os.listdir(descriptor) on a directory raised OSError, wrapped as a SandboxError with {relative} and {exc}. The snapshotter cannot enumerate the directory, so capture cannot be proven complete.
Source
Thrown at eval/workflow_bench/task_assets.py:394
budget: _SnapshotBudget | None = None,
allow_symlinks: bool = False,
preserve_modes: bool = False,
):
self.destination = destination
self.entries: dict[PurePosixPath, AssetManifestEntry] = {}
self.total_bytes = 0
self.budget = budget if budget is not None else _SnapshotBudget()
self.allow_symlinks = allow_symlinks
self.preserve_modes = preserve_modes
def copy_descriptor(self, descriptor: int, relative: PurePosixPath) -> None:
before = os.fstat(descriptor)
if stat.S_ISDIR(before.st_mode):
self._record_directory(relative)
try:
names = sorted(os.listdir(descriptor))
except OSError as exc:
raise SandboxError(f"sandbox_copy directory is unreadable: {relative}: {exc}") from exc
for name in names:
child_relative = relative / name
child_metadata = os.stat(name, dir_fd=descriptor, follow_symlinks=False)
if stat.S_ISLNK(child_metadata.st_mode):
if not self.allow_symlinks:
raise SandboxError(f"sandbox_copy must not traverse a symlink: {child_relative}")
self._copy_symlink(descriptor, name, child_relative, child_metadata)
continue
child = _open_child(descriptor, name, child_relative)
try:
self.copy_descriptor(child, child_relative)
finally:
os.close(child)
after = os.fstat(descriptor)
if _mutation_identity(before) != _mutation_identity(after):
raise SandboxError(f"sandbox_copy directory changed while snapshotting: {relative}")
return
if not stat.S_ISREG(before.st_mode):View on GitHub (pinned to d540b00184)
Solutions
- Inspect the chained OSError.errno (EACCES/EIO/ENOENT).
- Fix permissions on the source tree (read+execute on directories, read on files).
- Run on a reliable filesystem and ensure no concurrent deletion during capture.
- Re-run prepare once the tree is stable.
Defensive patterns
Strategy: try-catch
Validate before calling
import os
def assert_source_listable(repo, relative):
p = os.path.join(repo, *relative.parts)
try:
with os.scandir(p) as it:
list(it)
except OSError as exc:
raise RuntimeError(f"sandbox_copy dir unreadable: {relative}: {exc}") from exc Try / catch
from workflow_bench.proposer_sandbox import SandboxError
try:
snapshot = cache.prepare(task, repo=repo, resolved_sha=sha)
except SandboxError as exc:
if "directory is unreadable" in str(exc):
log.error("sandbox_copy permission/IO error - check dir r-x bits and filesystem health")
raise Prevention
- Ensure read+execute bits on every sandbox_copy source directory for the runner UID.
- Run on a reliable local filesystem, not a flaky mount.
- Prevent concurrent deletion during snapshot capture.
When it happens
Trigger: A sandbox_copy source directory lacks read/execute permission for the runner, or hits an IO error (EIO) or vanishes (ENOENT) during TaskAssetCache.prepare.
Common situations: Permission bits on a vendored/generated directory; a mount returning EIO; a directory removed mid-snapshot by a concurrent writer.
Related errors
- transcript artifact parent must be owner-only: {current}
- evidence source must be a regular non-symlink file: {path}
- results directory is unavailable: {root}: {exc}
- results directory must be a real non-symlink directory: {roo
- results directory must not traverse symlinks: {root}
AI-assisted analysis of abhigyanpatwari/GitNexus@d540b00184 (2026-08-12).
Data as JSON: /api/errors/d3f5e32271747a3c.
Report an issue: GitHub.