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

  1. Quiesce all writers against the plugin source, then re-run (a stable tree makes this check pass deterministically).
  2. Snapshot the plugin to an immutable location (cp -a into a fresh dir, chmod -R a-w) and pass that as --ce-plugin-dir.
  3. Run the benchmark against a released tarball extracted into a throwaway directory rather than a live checkout.
  4. 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

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


AI-assisted analysis of abhigyanpatwari/GitNexus@d540b00184 (2026-08-12). Data as JSON: /api/errors/03c267dd89e806db. Report an issue: GitHub.