{"record":{"id":"970c4632f6134919","repo":"abhigyanpatwari/GitNexus","slug":"phase-changed-unauthorized-workspace-path-s-pre","errorCode":null,"errorMessage":"phase changed unauthorized workspace path(s): {preview}{suffix}","messagePattern":"phase changed unauthorized workspace path\\(s\\): (.+?)(.+?)","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"eval/workflow_bench/runner_artifacts.py","lineNumber":240,"sourceCode":"            or metadata.st_dev != opened.st_dev\n            or metadata.st_ino != opened.st_ino\n        ):\n            raise ValueError(f\"phase artifact must be a regular non-symlink file: {relative}\")\n    finally:\n        os.close(descriptor)\n\n    allowed = {artifact_key}\n    parent = relative.parent\n    while parent.parts:\n        parent_key = parent.as_posix()\n        if parent_key not in before and after.get(parent_key, \"\").startswith(\"d:\"):\n            allowed.add(parent_key)\n        parent = parent.parent\n    unauthorized = sorted(changed - allowed)\n    if unauthorized:\n        preview = \", \".join(unauthorized[:8])\n        suffix = \" …\" if len(unauthorized) > 8 else \"\"\n        raise ValueError(f\"phase changed unauthorized workspace path(s): {preview}{suffix}\")\n\n\ndef require_skill_fingerprint(worktree: Path, arm: str, expected: str | None, *, phase: str) -> None:\n    \"\"\"Fail closed when a bounded phase changes the evaluated prompt roots.\"\"\"\n\n    try:\n        observed = skill_fingerprint(worktree, arm)\n    except (OSError, ValueError) as exc:\n        raise ValueError(f\"{phase} changed the evaluated skill fingerprint\") from exc\n    if observed != expected:\n        raise ValueError(f\"{phase} changed the evaluated skill fingerprint\")\n\n\ndef snapshot_plan_docs(worktree: Path) -> dict[Path, str]:\n    \"\"\"Hash direct, regular plan artifacts without following links.\"\"\"\n\n    plans = worktree / \"docs\" / \"plans\"\n    if not plans.exists():","sourceCodeStart":222,"sourceCodeEnd":258,"githubUrl":"https://github.com/abhigyanpatwari/GitNexus/blob/d540b00184d71a896261ee02670da9a92d59d8f7/eval/workflow_bench/runner_artifacts.py#L222-L258","documentation":"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.","triggerScenarios":"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.","commonSituations":"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).","solutions":["Inspect the {preview} paths; remove or relocate them (the model should only touch the declared artifact).","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.","Tighten the prompt/tooling so the phase writes nothing but its artifact; run builds/logs to a directory outside the hashed worktree."],"exampleFix":"# before: a verifier writes build output into the worktree\nverify: npm run build   # emits dist/\n\n# after: build outside the hashed worktree\nverify: npm run build -- --outDir /tmp/build && cp -r /tmp/dist ./dist\n# or exclude dist via .gitignore if the snapshot should ignore it","handlingStrategy":"validation","validationCode":"from pathlib import Path\nfrom eval.workflow_bench.runner_artifacts import workspace_snapshot\n\n# Take an immediate after-snapshot and diff against `before` to preview\n# unauthorized changes BEFORE enforce_phase_workspace raises.\nbefore = {...}  # captured before the phase\nafter = workspace_snapshot(Path(worktree).resolve())\nartifact_key = Path(allowed_artifact).resolve().relative_to(Path(worktree).resolve()).as_posix()\nchanged = {p for p in before.keys() | after.keys() if before.get(p) != after.get(p)}\nunauthorized = sorted(changed - {artifact_key})\nassert not unauthorized, f\"phase will be rejected for unauthorized changes: {unauthorized[:8]}\"","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Constrain the phase prompt to touch only the declared artifact.","If a path is tool noise the model did not choose to write, extend CLAUDE_BOOTSTRAP_ENTRIES / WORKSPACE_SNAPSHOT_BOOTSTRAP_NOISE from the observed failure (never pre-emptively).","Run builds/logs outside the hashed worktree."],"tags":["phase-boundary","unauthorized-change","workspace","integrity"],"backgroundTag":null,"analyzedSha":"d540b00184d71a896261ee02670da9a92d59d8f7","analyzedAt":"2026-08-12T19:50:25.132Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}