abhigyanpatwari/GitNexus · error · ValueError

workspace file changed while hashing: {entry.path}

Error message

workspace file changed while hashing: {entry.path}

What it means

Raised in workspace_snapshot (runner_artifacts.py:181) after hashing a file: os.fstat is taken again and if size or mtime_ns changed between the pre-hash fstat and the post-hash fstat the file is considered mutated during hashing. The guard ensures the computed sha256 actually corresponds to stable content; a mid-read change would yield a meaningless hash.

Source

Thrown at eval/workflow_bench/runner_artifacts.py:181

                continue
            file_bytes += metadata.st_size
            if file_bytes > MAX_WORKSPACE_SNAPSHOT_FILE_BYTES:
                raise ValueError("workspace snapshot exceeds its bounded file-byte limit")
            descriptor = os.open(entry.path, os.O_RDONLY | nofollow)
            try:
                opened = os.fstat(descriptor)
                if (
                    not stat.S_ISREG(opened.st_mode)
                    or opened.st_dev != metadata.st_dev
                    or opened.st_ino != metadata.st_ino
                ):
                    raise ValueError(f"workspace file changed while opening: {entry.path}")
                digest = hashlib.sha256()
                while chunk := os.read(descriptor, 64 * 1024):
                    digest.update(chunk)
                after = os.fstat(descriptor)
                if (opened.st_size, opened.st_mtime_ns) != (after.st_size, after.st_mtime_ns):
                    raise ValueError(f"workspace file changed while hashing: {entry.path}")
            finally:
                os.close(descriptor)
            snapshot[relative.as_posix()] = f"f:{permissions:o}:{metadata.st_size}:{digest.hexdigest()}"
    return snapshot


def enforce_phase_workspace(
    worktree: Path,
    before: dict[str, str],
    *,
    allowed_artifact: Path,
) -> None:
    """Require a phase to change only its one explicit workspace artifact."""

    root = worktree.expanduser().absolute()
    artifact = allowed_artifact.expanduser().absolute()
    try:
        relative = PurePosixPath(artifact.relative_to(root).as_posix())

View on GitHub (pinned to d540b00184)

Solutions

  1. Ensure the phase is quiescent before snapshotting — the model/build must have exited and flushed. Check for stray processes writing to the worktree (lsof +D <worktree>).
  2. If the file is a live log, exclude it by moving it under a bootstrap-noise path or out of the worktree.
  3. Re-run the snapshot; transient concurrency races usually clear once the writer is gone.
Defensive patterns

Strategy: try-catch

Try / catch

try:
    snapshot = workspace_snapshot(root)
except ValueError as exc:
    if "changed while hashing" in str(exc):
        # File was appended/truncated during the read loop.
        wait_for_quiescence(root)
        snapshot = workspace_snapshot(root)
    else:
        raise

Prevention

When it happens

Trigger: A process writes to the file while os.read loops over its 64 KiB chunks: between the pre-read fstat (line 169) and the post-read fstat (line 179) either st_size or st_mtime_ns differs. Happens when a log file, build output, or model artifact is appended to during the snapshot.

Common situations: A task is still writing logs/output when the phase-boundary snapshot runs; an editor autosaving; a daemon appending to a file inside the worktree; concurrent arms sharing a directory they should not.

Related errors


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