abhigyanpatwari/GitNexus · error · SandboxError

sanitized graph source changed while opening: {relative}

Error message

sanitized graph source changed while opening: {relative}

What it means

A TOCTOU guard in the scrubber: after entry.stat() and after os.open+os.fstat, the inode's (st_dev, st_ino, st_size) differs from the stat snapshot, or the descriptor is no longer a regular file. The harness treats any divergence as proof the seed is being mutated and aborts instead of reading uncontrolled bytes.

Source

Thrown at eval/workflow_bench/sanitized_graph.py:205

            if stat.S_ISDIR(metadata.st_mode):
                pending.append((Path(entry.path), relative))
                continue
            if stat.S_ISLNK(metadata.st_mode) or not stat.S_ISREG(metadata.st_mode):
                continue
            if metadata.st_size > MAX_GRAPH_SCRUB_FILE_BYTES:
                continue
            scanned_bytes += metadata.st_size
            if scanned_bytes > MAX_GRAPH_SCRUB_TOTAL_BYTES:
                raise SandboxError("sanitized graph source exceeds the scrub byte limit")
            descriptor = os.open(entry.path, os.O_RDONLY | getattr(os, "O_NOFOLLOW", 0))
            try:
                opened = os.fstat(descriptor)
                if not stat.S_ISREG(opened.st_mode) or (opened.st_dev, opened.st_ino, opened.st_size) != (
                    metadata.st_dev,
                    metadata.st_ino,
                    metadata.st_size,
                ):
                    raise SandboxError(f"sanitized graph source changed while opening: {relative}")
                chunks: list[bytes] = []
                remaining = MAX_GRAPH_SCRUB_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)
                if len(payload) != opened.st_size or (opened.st_size, opened.st_mtime_ns, opened.st_ctime_ns) != (
                    after.st_size,
                    after.st_mtime_ns,
                    after.st_ctime_ns,
                ):
                    raise SandboxError(f"sanitized graph source changed while scanning: {relative}")
            finally:
                os.close(descriptor)

View on GitHub (pinned to d540b00184)

Solutions

  1. Guarantee single-writer access to the seed worktree for the whole scrub+build window; the harness already creates one disposable seed per task.
  2. Run on a quiescent filesystem with no indexer/AV/sync daemon touching the clone.
  3. Retry prepare_sanitized_graph once the concurrent writer is gone; the seed is rebuilt fresh by make_worktree.
Defensive patterns

Strategy: validation

Validate before calling

import fcntl, os

def take_tree_lock(path):
    """Acquire an exclusive flock on the seed root to prevent concurrent mutation."""
    fd = os.open(path, os.O_RDONLY | os.O_DIRECTORY)
    fcntl.flock(fd, fcntl.LOCK_EX)
    return fd

Prevention

When it happens

Trigger: A file in the seed is replaced/rewritten between the DirEntry.stat call and the os.fstat after os.open inside _scrub_source_references.

Common situations: An IDE/git/indexer touching the worktree mid-scrub; two benchmark arms sharing a seed directory; an antivirus or sync daemon rewriting files in place.

Related errors


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