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
- Guarantee single-writer access to the seed worktree for the whole scrub+build window; the harness already creates one disposable seed per task.
- Run on a quiescent filesystem with no indexer/AV/sync daemon touching the clone.
- 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
- Guarantee single-writer access to the seed for the whole scrub+build window.
- Run on a quiescent filesystem with no indexer, AV, or sync daemon.
- Retry prepare_sanitized_graph after stopping any concurrent writer; the seed is rebuilt fresh.
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
- sanitized graph source changed while scanning: {relative}
- sandbox_copy directory changed while snapshotting: {relative
- {label} is unreadable: {path}: {exc}
- {label} changed while opening: {path}
- evidence source must be a regular non-symlink file: {path}
AI-assisted analysis of abhigyanpatwari/GitNexus@d540b00184 (2026-08-12).
Data as JSON: /api/errors/62478759a8a661af.
Report an issue: GitHub.