abhigyanpatwari/GitNexus · critical · RuntimeError
generated artifact changed type while opening: {path}
Error message
generated artifact changed type while opening: {path} What it means
Thrown by _bounded_regular_bytes as a TOCTOU guard: after the lstat accepted the path as a regular non-symlink file, the path is opened with O_RDONLY|O_NOFOLLOW and the descriptor is fstat'd. If the now-open fd is not a regular file, something swapped the path's target between the lstat and the open, and the read is aborted.
Source
Thrown at eval/workflow_bench/runner_artifacts.py:442
":(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)
def capture_patch(sandbox: SandboxSession, worktree: Path, orig_sha: str) -> bytes:
"""Stream a final patch inside the sandbox while retaining a bounded prefix."""
artifact_dir = Path(tempfile.mkdtemp(prefix=".wfbench-artifact-", dir=worktree))
artifact_dir.chmod(0o700)View on GitHub (pinned to d540b00184)
Solutions
- Quiesce the worktree (agent exited, no concurrent writer) before _bounded_regular_bytes runs.
- Run on a local filesystem where the open reflects the lstat atomically enough.
- If the swap is a legitimate agent rewrite, ensure capture happens only after the agent has fully released the file.
- Re-run the arm; genuine TOCTOU swaps require an active concurrent writer.
Example fix
// before — capturing while agent still finalizing patch = capture_patch(sandbox, worktree, orig_sha) # agent rewrites mid-capture // after — join agent, then capture a stable file agent_proc.wait() patch = capture_patch(sandbox, worktree, orig_sha)
Defensive patterns
Strategy: validation
Validate before calling
import os, stat
from pathlib import Path
def open_is_stable_type(path: Path) -> bool:
nofollow = getattr(os, 'O_NOFOLLOW', 0)
try:
fd = os.open(path, os.O_RDONLY | nofollow)
except OSError:
return False
try:
return stat.S_ISREG(os.fstat(fd).st_mode)
finally:
os.close(fd) Type guard
null
Try / catch
try:
data = _bounded_regular_bytes(patch, limit=MAX_PATCH_BYTES)
except RuntimeError as e:
if 'changed type while opening' in str(e):
# TOCTOU type swap; quiesce the writer and retry
raise
raise Prevention
- Quiesce the worktree (agent exited) before reading artifacts.
- Run on a local filesystem where open reflects lstat atomically.
- Treat this as evidence of an active concurrent writer — investigate, do not blindly retry.
- Capture artifacts only after the producing process has closed the file.
When it happens
Trigger: os.fstat(descriptor) on the opened fd reports not S_ISREG. Between path.lstat() and os.open(...), the path was replaced (e.g., a regular file was unlinked and a special file or symlink target put in its place, then opened).
Common situations: A concurrent process races to swap the artifact type during capture; the agent's own finalize step rewrites the patch mid-read; a filesystem quirk where O_NOFOLLOW still opened something unexpected.
Related errors
- generated artifact is not a regular non-symlink file: {path}
- {label} changed while opening: {path}
- evidence source must be a regular non-symlink file: {path}
- results directory must not traverse symlinks: {root}
- results artifact parent must be a real directory: {current}
AI-assisted analysis of abhigyanpatwari/GitNexus@d540b00184 (2026-08-12).
Data as JSON: /api/errors/cb125e215296d8a5.
Report an issue: GitHub.