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

  1. Construct allowed_artifact as a path inside the worktree, e.g. worktree / 'docs' / 'plans' / 'plan.md', and pass that.
  2. If the artifact must live outside, copy/symlink it into the worktree first and point allowed_artifact at the in-worktree location.
  3. 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

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


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