abhigyanpatwari/GitNexus · error · ValueError

phase changed unauthorized workspace path(s): {preview}{suff

Error message

phase changed unauthorized workspace path(s): {preview}{suffix}

What it means

Raised by enforce_phase_workspace (runner_artifacts.py:240) when the set of changed workspace paths (before vs after snapshot) minus the allowed set (the artifact and any new parent directories) is non-empty. The phase contract permits changing only the one declared artifact; any other change (stray file, edit outside the artifact, new directory) is unauthorized and the phase fails.

Source

Thrown at eval/workflow_bench/runner_artifacts.py:240

            or metadata.st_dev != opened.st_dev
            or metadata.st_ino != opened.st_ino
        ):
            raise ValueError(f"phase artifact must be a regular non-symlink file: {relative}")
    finally:
        os.close(descriptor)

    allowed = {artifact_key}
    parent = relative.parent
    while parent.parts:
        parent_key = parent.as_posix()
        if parent_key not in before and after.get(parent_key, "").startswith("d:"):
            allowed.add(parent_key)
        parent = parent.parent
    unauthorized = sorted(changed - allowed)
    if unauthorized:
        preview = ", ".join(unauthorized[:8])
        suffix = " …" if len(unauthorized) > 8 else ""
        raise ValueError(f"phase changed unauthorized workspace path(s): {preview}{suffix}")


def require_skill_fingerprint(worktree: Path, arm: str, expected: str | None, *, phase: str) -> None:
    """Fail closed when a bounded phase changes the evaluated prompt roots."""

    try:
        observed = skill_fingerprint(worktree, arm)
    except (OSError, ValueError) as exc:
        raise ValueError(f"{phase} changed the evaluated skill fingerprint") from exc
    if observed != expected:
        raise ValueError(f"{phase} changed the evaluated skill fingerprint")


def snapshot_plan_docs(worktree: Path) -> dict[Path, str]:
    """Hash direct, regular plan artifacts without following links."""

    plans = worktree / "docs" / "plans"
    if not plans.exists():

View on GitHub (pinned to d540b00184)

Solutions

  1. Inspect the {preview} paths; remove or relocate them (the model should only touch the declared artifact).
  2. If a path is genuine tool noise that the model did not choose to write (like Claude Code's .cc-writes), add it to CLAUDE_BOOTSTRAP_ENTRIES or WORKSPACE_SNAPSHOT_BOOTSTRAP_NOISE per the comment at runner_artifacts.py:69-78 — extend only from an observed failure, never pre-emptively.
  3. Tighten the prompt/tooling so the phase writes nothing but its artifact; run builds/logs to a directory outside the hashed worktree.

Example fix

# before: a verifier writes build output into the worktree
verify: npm run build   # emits dist/

# after: build outside the hashed worktree
verify: npm run build -- --outDir /tmp/build && cp -r /tmp/dist ./dist
# or exclude dist via .gitignore if the snapshot should ignore it
Defensive patterns

Strategy: validation

Validate before calling

from pathlib import Path
from eval.workflow_bench.runner_artifacts import workspace_snapshot

# Take an immediate after-snapshot and diff against `before` to preview
# unauthorized changes BEFORE enforce_phase_workspace raises.
before = {...}  # captured before the phase
after = workspace_snapshot(Path(worktree).resolve())
artifact_key = Path(allowed_artifact).resolve().relative_to(Path(worktree).resolve()).as_posix()
changed = {p for p in before.keys() | after.keys() if before.get(p) != after.get(p)}
unauthorized = sorted(changed - {artifact_key})
assert not unauthorized, f"phase will be rejected for unauthorized changes: {unauthorized[:8]}"

Prevention

When it happens

Trigger: sorted(changed - allowed) is non-empty at line 237. The model wrote files outside the artifact path, left temp files, edited unrelated tracked files, or a tool it invoked created build outputs/logs in the worktree. The message lists up to 8 offending paths with a trailing ellipsis if more.

Common situations: The model edited source files during a planning-only phase; a verifier/build wrote artifacts into the worktree; the model left .log/.tmp files; Claude Code's bootstrap noise was not fully covered by the exclusion set (extend WORKSPACE_SNAPSHOT_BOOTSTRAP_NOISE / CLAUDE_BOOTSTRAP_ENTRIES from an observed failure).

Understand the failure class

Related errors


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