abhigyanpatwari/GitNexus · error · SandboxError
Compound Engineering plugin file is unreadable: {path}: {exc
Error message
Compound Engineering plugin file is unreadable: {path}: {exc} What it means
_bounded_plugin_bytes() lstat's each plugin file before opening it with O_NOFOLLOW to copy the bytes into the snapshot. OSError on that initial lstat (file removed, permission denied, I/O error) raises this error so the harness never silently skips a file the walk already emitted.
Source
Thrown at eval/workflow_bench/runtime_mounts.py:354
directory = source / name
if not directory.exists():
continue
try:
metadata = directory.lstat()
except OSError as exc:
raise SandboxError(f"Compound Engineering plugin component is unreadable: {directory}: {exc}") from exc
if stat.S_ISLNK(metadata.st_mode) or not stat.S_ISDIR(metadata.st_mode):
raise SandboxError(f"Compound Engineering plugin component must be a real directory: {directory}")
yield from walk(directory, PurePosixPath(name))
def _bounded_plugin_bytes(path: Path) -> tuple[bytes, bool]:
"""Read one stable regular file without following a last-component symlink."""
try:
before = path.lstat()
except OSError as exc:
raise SandboxError(f"Compound Engineering plugin file is unreadable: {path}: {exc}") from exc
if stat.S_ISLNK(before.st_mode) or not stat.S_ISREG(before.st_mode):
raise SandboxError(f"Compound Engineering plugin file must be regular and non-symlink: {path}")
if before.st_size > MAX_CE_PLUGIN_FILE_BYTES:
raise SandboxError(f"Compound Engineering plugin file exceeds the per-file limit: {path}")
descriptor = os.open(path, os.O_RDONLY | getattr(os, "O_NOFOLLOW", 0))
try:
opened = os.fstat(descriptor)
if (opened.st_dev, opened.st_ino) != (before.st_dev, before.st_ino) or not stat.S_ISREG(opened.st_mode):
raise SandboxError(f"Compound Engineering plugin file changed during validation: {path}")
chunks: list[bytes] = []
remaining = MAX_CE_PLUGIN_FILE_BYTES + 1
while remaining > 0:
chunk = os.read(descriptor, min(64 * 1024, remaining))
if not chunk:
break
chunks.append(chunk)
remaining -= len(chunk)
payload = b"".join(chunks)View on GitHub (pinned to d540b00184)
Solutions
- Freeze the plugin tree before running the benchmark: stop concurrent writers, copy to a local readonly directory.
- Grant read on every file: `chmod -R +r <plugin_dir>`.
- Re-run the snapshot build against the stable copy.
Defensive patterns
Strategy: validation
Validate before calling
from pathlib import Path
def all_files_readable(source: Path) -> bool:
for name in ("skills", "scripts", "assets"):
d = source / name
if not d.exists():
continue
for p in d.rglob("*"):
if p.is_file():
try:
p.lstat()
except OSError:
return False
return True Try / catch
try:
snapshot = _build_ce_plugin_snapshot(config, parent)
except SandboxError as exc:
if "file is unreadable" in str(exc):
# freeze tree + grant read, then retry
...
raise Prevention
- Freeze the plugin source during the snapshot build (no concurrent writes).
- Copy the plugin to a local readonly directory and benchmark against the copy.
- Ensure `chmod -R +r` covers every regular file in the source tree.
When it happens
Trigger: A file emitted by walk() is removed or has its permissions changed before _bounded_plugin_bytes() runs; NFS stale handle; concurrent writer.
Common situations: Concurrent build/editing of the plugin tree during snapshot creation; ACL applied between enumeration and copy; flaky network filesystem.
Related errors
- Compound Engineering plugin entry is unreadable: {entry.path
- Compound Engineering plugin directory is unreadable: {direct
- Compound Engineering plugin component is unreadable: {direct
- Could not read ${GITNEXUS_RC_FILENAME}: ${(err as Error).mes
- candidate destination is unreadable: {relative}: {exc}
AI-assisted analysis of abhigyanpatwari/GitNexus@d540b00184 (2026-08-12).
Data as JSON: /api/errors/423f1450fae4a558.
Report an issue: GitHub.