abhigyanpatwari/GitNexus · critical · SandboxError
Compound Engineering plugin file changed while being copied:
Error message
Compound Engineering plugin file changed while being copied: {path} What it means
Final stability check in _bounded_plugin_bytes. After the read completes, the (dev, ino, size, mtime_ns) tuple from the post-open fstat is compared to the post-read fstat, and the payload length is checked against the final size. Any drift means the file was modified mid-copy, so the bytes read cannot be trusted as the plugin's stable content.
Source
Thrown at eval/workflow_bench/runtime_mounts.py:381
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)
after = os.fstat(descriptor)
finally:
os.close(descriptor)
if len(payload) > MAX_CE_PLUGIN_FILE_BYTES:
raise SandboxError(f"Compound Engineering plugin file exceeds the per-file limit: {path}")
identity_before = (opened.st_dev, opened.st_ino, opened.st_size, opened.st_mtime_ns)
identity_after = (after.st_dev, after.st_ino, after.st_size, after.st_mtime_ns)
if identity_after != identity_before or len(payload) != after.st_size:
raise SandboxError(f"Compound Engineering plugin file changed while being copied: {path}")
return payload, bool(before.st_mode & 0o111)
def _write_snapshot_file(path: Path, payload: bytes, *, executable: bool) -> None:
path.parent.mkdir(parents=True, exist_ok=True)
descriptor = os.open(
path,
os.O_WRONLY | os.O_CREAT | os.O_EXCL | getattr(os, "O_NOFOLLOW", 0),
0o500 if executable else 0o400,
)
try:
view = memoryview(payload)
while view:
written = os.write(descriptor, view)
view = view[written:]
os.fchmod(descriptor, 0o555 if executable else 0o444)
finally:
os.close(descriptor)View on GitHub (pinned to d540b00184)
Solutions
- Quiesce all writers against the plugin source, then re-run (a stable tree makes this check pass deterministically).
- Snapshot the plugin to an immutable location (cp -a into a fresh dir, chmod -R a-w) and pass that as --ce-plugin-dir.
- Run the benchmark against a released tarball extracted into a throwaway directory rather than a live checkout.
- If you control the writer, have it write to a temp path and atomic-rename once, outside the snapshot window.
Example fix
# before wfbench run --ce-plugin-dir ./ce-plugin # live checkout, editor open # after git -C ./ce-plugin archive --prefix=ce-plugin-frozen/ HEAD | tar -x -C /tmp wfbench run --ce-plugin-dir /tmp/ce-plugin-frozen
Defensive patterns
Strategy: validation
Validate before calling
from pathlib import Path
import shutil, os, stat
src = Path("ce-plugin")
frozen = Path("/tmp/ce-plugin-frozen")
if frozen.exists():
shutil.rmtree(frozen)
shutil.copytree(src, frozen, symlinks=False)
# make immutable so mtime cannot change mid-snapshot
for root, _, files in os.walk(frozen):
Path(root).chmod(0o555)
for f in files:
Path(root, f).chmod(0o444)
# pass frozen as --ce-plugin-dir Type guard
import os
from pathlib import Path
def is_stable_during_read(path: Path) -> bool:
try:
before = path.lstat()
fd = os.open(path, os.O_RDONLY | getattr(os, "O_NOFOLLOW", 0))
a = os.fstat(fd)
data = b""
while True:
chunk = os.read(fd, 64 * 1024)
if not chunk:
break
data += chunk
b = os.fstat(fd)
os.close(fd)
except OSError:
return False
return (a.st_size, a.st_mtime_ns) == (b.st_size, b.st_mtime_ns) and len(data) == b.st_size Try / catch
try:
snapshot = _build_ce_plugin_snapshot(config, destination_parent)
except SandboxError as exc:
if "changed while being copied" in str(exc):
log.error("plugin file modified mid-copy; freeze source and retry")
raise Prevention
- Freeze the plugin into an immutable copy before benchmarking.
- Close editors and stop watchers touching the plugin tree.
- Run against a released tarball extracted fresh per run.
When it happens
Trigger: The file was rewritten (size, mtime, or inode changed) between the start and end of the read loop; the writer did not just append but replaced the inode (atomic rename) or truncated and rewrote.
Common situations: Editor save during snapshot; 'npm install' rewriting package files in place; a build tool using atomic rename to publish into scripts/; rsync running against the same tree.
Related errors
- Compound Engineering plugin file changed during validation:
- {label} is unreadable: {path}: {exc}
- {label} changed while opening: {path}
- transcript artifact changed while opening: {path}
- transcript artifact changed while reading: {path}
AI-assisted analysis of abhigyanpatwari/GitNexus@d540b00184 (2026-08-12).
Data as JSON: /api/errors/03c267dd89e806db.
Report an issue: GitHub.