abhigyanpatwari/GitNexus · error · ValueError
phase artifact escapes the workspace: {allowed_artifact}
Error message
phase artifact escapes the workspace: {allowed_artifact} What it means
Raised by enforce_phase_workspace (runner_artifacts.py:201) when the allowed_artifact path cannot be expressed relative to the workspace root. The phase contract requires the artifact to live strictly inside the worktree; an artifact outside it (absolute path on the host, or reachable only via '..') cannot be diffed against the workspace snapshot and is rejected.
Source
Thrown at eval/workflow_bench/runner_artifacts.py:201
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())
except ValueError as exc:
raise ValueError(f"phase artifact escapes the workspace: {allowed_artifact}") from exc
after = workspace_snapshot(root)
changed = {path for path in before.keys() | after.keys() if before.get(path) != after.get(path)}
artifact_key = relative.as_posix()
artifact_state = after.get(artifact_key)
if before.get(artifact_key) == artifact_state:
raise ValueError(f"phase did not create or change its required artifact: {relative}")
if artifact_state is None or not artifact_state.startswith("f:"):
raise ValueError(f"phase artifact must be a regular non-symlink file: {relative}")
try:
metadata = artifact.lstat()
descriptor = os.open(artifact, os.O_RDONLY | getattr(os, "O_NOFOLLOW", 0))
except OSError as exc:
raise ValueError(f"phase artifact must be a readable regular non-symlink file: {relative}") from exc
try:
opened = os.fstat(descriptor)
if (
stat.S_ISLNK(metadata.st_mode)View on GitHub (pinned to d540b00184)
Solutions
- Construct allowed_artifact as a path inside the worktree, e.g. worktree / 'docs' / 'plans' / 'plan.md', and pass that.
- If the artifact must live outside, copy/symlink it into the worktree first and point allowed_artifact at the in-worktree location.
- Double-check that worktree and allowed_artifact are both expanded/absolute and that artifact is genuinely under root (artifact.relative_to(root) should not raise).
Example fix
# before
enforce_phase_workspace(worktree, before, allowed_artifact=Path('/tmp/plan.md'))
# after
artifact = worktree / 'docs' / 'plans' / 'plan.md'
enforce_phase_workspace(worktree, before, allowed_artifact=artifact) Defensive patterns
Strategy: validation
Validate before calling
from pathlib import Path
root = Path(worktree).expanduser().absolute()
artifact = Path(allowed_artifact).expanduser().absolute()
# Must be inside root, or relative_to raises
rel = artifact.relative_to(root)
assert rel.parts and ".." not in rel.parts, (
f"artifact {artifact} is not strictly inside worktree root {root}") Type guard
from pathlib import Path
def is_inside_worktree(artifact, root) -> bool:
root = Path(root).expanduser().absolute()
artifact = Path(artifact).expanduser().absolute()
try:
artifact.relative_to(root)
return True
except ValueError:
return False Prevention
- Construct allowed_artifact as worktree / <rel-path>, never an absolute host path.
- Pass already-resolved (resolve()) paths for both worktree and artifact.
When it happens
Trigger: PurePosixPath(artifact.relative_to(root)) raises ValueError because artifact is not under root. Caused by passing an allowed_artifact that is an absolute host path, a symlink that resolves outside the worktree, or a path constructed with '..' segments.
Common situations: Caller computes allowed_artifact from a tempfile outside the worktree; the artifact was written to /tmp instead of inside the clone; the worktree root was passed un-expanded so the relative computation mismatches.
Related errors
- phase changed unauthorized workspace path(s): {preview}{suff
- phase artifact must be a regular non-symlink file: {relative
- {phase} changed the evaluated skill fingerprint
- Analyzer build or dependency runtime changed during analysis
- Search index build failed with an integrity error and the an
AI-assisted analysis of abhigyanpatwari/GitNexus@d540b00184 (2026-08-12).
Data as JSON: /api/errors/8476051970932d88.
Report an issue: GitHub.