abhigyanpatwari/GitNexus · critical · RuntimeError

generated artifact is not a regular non-symlink file: {path}

Error message

generated artifact is not a regular non-symlink file: {path}

What it means

Thrown by _bounded_regular_bytes (used when reading the captured final patch) at the lstat gate. The harness refuses to follow a symlink or read a non-regular file as a generated artifact, because a symlink could point outside the worktree and a special file could exfiltrate or corrupt data. Only a plain regular file is accepted.

Source

Thrown at eval/workflow_bench/runner_artifacts.py:436

            "--no-ext-diff",
            "--no-textconv",
            "--shortstat",
            orig_sha,
            "--",
            ".",
            ":(exclude)docs/plans",
            ":(exclude).claude/skills",
        ],
    )
    return parse_shortstat(output)


def _bounded_regular_bytes(path: Path, *, limit: int) -> bytes:
    """Read at most ``limit`` bytes without following a generated link."""

    metadata = path.lstat()
    if stat.S_ISLNK(metadata.st_mode) or not stat.S_ISREG(metadata.st_mode):
        raise RuntimeError(f"generated artifact is not a regular non-symlink file: {path}")
    nofollow = getattr(os, "O_NOFOLLOW", 0)
    descriptor = os.open(path, os.O_RDONLY | nofollow)
    try:
        opened = os.fstat(descriptor)
        if not stat.S_ISREG(opened.st_mode):
            raise RuntimeError(f"generated artifact changed type while opening: {path}")
        chunks: list[bytes] = []
        remaining = limit
        while remaining > 0:
            chunk = os.read(descriptor, min(64 * 1024, remaining))
            if not chunk:
                break
            chunks.append(chunk)
            remaining -= len(chunk)
        return b"".join(chunks)
    finally:
        os.close(descriptor)

View on GitHub (pinned to d540b00184)

Solutions

  1. Have the agent write the patch as a real file (open in 'xb'/'wb' mode), not via ln -s.
  2. Remove any pre-existing symlink at the patch path before capture.
  3. Audit agent tooling for shell `ln -s` usage against artifact paths.
  4. If the agent legitimately needs to reference another file, copy its bytes rather than linking.

Example fix

// before — agent symlinks the patch
os.symlink('/host/secret', worktree/'final.patch')

// after — write real bytes
(worktree/'final.patch').write_bytes(patch_bytes)
Defensive patterns

Strategy: validation

Validate before calling

import stat
from pathlib import Path

def is_regular_nonsymlink(path: Path) -> bool:
    try:
        st = path.lstat()
    except OSError:
        return False
    return stat.S_ISREG(st.st_mode)

Type guard

import stat
from pathlib import Path

def is_regular_nonsymlink(path: Path) -> bool:
    try:
        st = path.lstat()
    except OSError:
        return False
    return stat.S_ISREG(st.st_mode)

Try / catch

try:
    data = _bounded_regular_bytes(patch, limit=MAX_PATCH_BYTES)
except RuntimeError as e:
    if 'not a regular non-symlink file' in str(e):
        # the agent produced a symlink/special file; rewrite as real bytes
        raise
    raise

Prevention

When it happens

Trigger: path.lstat() reports S_ISLNK (the patch is a symlink) or not S_ISREG (it's a directory/socket/device/fifo). The agent-created patch is not a real file.

Common situations: The agent symlinked final.patch to /etc/passwd or another clone's patch; the patch path names a directory; a fifo/socket was created by a buggy tool; the artifact path collides with an existing directory.

Related errors


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