abhigyanpatwari/GitNexus · critical · SandboxError

transcript artifact must be owner-only: {path}

Error message

transcript artifact must be owner-only: {path}

What it means

Raised by `_bound_transcript_artifact` when the transcript file's permission bits include any group/other access (`mode & 0o077` is nonzero). Transcripts must be owner-only so no other local user can read proposer evidence.

Source

Thrown at eval/workflow_bench/evolve.py:396

            if normalized in seen_paths:
                raise SandboxError(f"duplicate transcript artifact path: {normalized}")
            seen_paths.add(normalized)
        artifacts_by_row.append(artifacts)
    return artifacts_by_row


def _bound_transcript_artifact(root: Path, metadata: Any) -> str:
    relative, expected_digest, expected_size = _transcript_artifact_metadata(metadata)

    path = _results_artifact_path(root, relative, transcript=True)
    try:
        before = path.lstat()
    except OSError as exc:
        raise SandboxError(f"transcript artifact is unavailable: {path}: {exc}") from exc
    if stat.S_ISLNK(before.st_mode) or not stat.S_ISREG(before.st_mode):
        raise SandboxError(f"transcript artifact must be a regular non-symlink file: {path}")
    if stat.S_IMODE(before.st_mode) & 0o077:
        raise SandboxError(f"transcript artifact must be owner-only: {path}")
    if before.st_size != expected_size:
        raise SandboxError(f"transcript artifact size does not match its results row: {path}")

    descriptor = os.open(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 != before.st_dev or opened.st_ino != before.st_ino:
            raise SandboxError(f"transcript artifact changed while opening: {path}")
        digest = hashlib.sha256()
        content = bytearray()
        while chunk := os.read(descriptor, 64 * 1024):
            digest.update(chunk)
            content.extend(chunk)
            if len(content) > MAX_EVIDENCE_FILE_BYTES:
                del content[: len(content) - MAX_EVIDENCE_FILE_BYTES]
        after = os.fstat(descriptor)
        if (opened.st_size, opened.st_mtime_ns) != (after.st_size, after.st_mtime_ns):
            raise SandboxError(f"transcript artifact changed while reading: {path}")

View on GitHub (pinned to d540b00184)

Solutions

  1. Run `chmod go-rwx` (chmod 0600) on every file under the results dir's transcripts/ subtree.
  2. Re-run the benchmark so the runner writes owner-only files directly.
  3. Fix the umask (e.g. umask 0077) of whatever process stages the results dir.

Example fix

// before: transcripts/foo.jsonl has mode 0644
chmod -R go-rwx <seed-results>/transcripts
// after: mode 0600 (owner-only)
Defensive patterns

Strategy: validation

Validate before calling

import stat
leaky = []
for r in evidence:
    for a in r.get('transcript_artifacts', []):
        m = (results_dir / a['path']).lstat().st_mode
        if stat.S_IMODE(m) & 0o077:
            leaky.append((a['path'], oct(stat.S_IMODE(m))))
if leaky:
    raise PermissionError(f'transcripts not owner-only: {leaky}')

Prevention

When it happens

Trigger: A transcript file with mode like 0644 or 0660 (any bit in group or other) under transcripts/. Common after unpacking a tarball that dropped owner-only bits.

Common situations: Extracting results from an archive without `--no-same-permissions` corrected, copying through a shared volume that relaxes modes, or a umask of 0022 producing 0644 files.

Related errors


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