abhigyanpatwari/GitNexus · error · ValueError
planning must create or modify exactly one plan artifact; ob
Error message
planning must create or modify exactly one plan artifact; observed {len(changed)} What it means
Thrown by new_plan_doc when the diff of before/after plan digests does not contain exactly one changed path. The harness pins each planning step to produce a single, unambiguous plan artifact; zero changed (planning did nothing observable) or more than one (planning scattered changes) both fail the evidence contract.
Source
Thrown at eval/workflow_bench/runner_artifacts.py:296
snapshot[path] = hashlib.file_digest(handle, "sha256").hexdigest()
after = os.fstat(descriptor)
if (opened.st_size, opened.st_mtime_ns) != (after.st_size, after.st_mtime_ns):
raise ValueError(f"plan artifact changed while hashing: {path}")
finally:
os.close(descriptor)
return snapshot
def new_plan_doc(worktree: Path, before: dict[Path, str]) -> Path:
"""Return the sole new or modified plan, rejecting ambiguous evidence."""
after = snapshot_plan_docs(worktree)
deleted = sorted(path for path in before if path not in after)
if deleted:
raise ValueError("planning deleted existing plan artifact(s): " + ", ".join(str(path) for path in deleted))
changed = sorted(path for path, digest in after.items() if before.get(path) != digest)
if len(changed) != 1:
raise ValueError(f"planning must create or modify exactly one plan artifact; observed {len(changed)}")
return changed[0]
def make_worktree(repo: Path, ref: str, parent: Path) -> Path:
"""Create a self-contained clone per benchmark arm."""
target = Path(tempfile.mkdtemp(prefix="wfbench-", dir=parent))
target.rmdir()
try:
run_checked(
[
"git",
"clone",
"--no-local",
"--no-hardlinks",
"--no-tags",
"--quiet",
str(repo),View on GitHub (pinned to d540b00184)
Solutions
- Confirm the plan is written under the worktree's docs/plans/ as a .md or .html file — other extensions and locations are filtered out by snapshot_plan_docs.
- Ensure the planning step produces exactly one new or modified plan file.
- If the legitimately created plan is not detected, check it is a regular file (not a symlink) and has a .md/.html suffix.
- Merge multiple plan fragments into a single document before the snapshot is taken.
Example fix
// before — agent writes two plan fragments (docs/plans / 'a.md').write_text(part_a) (docs/plans / 'b.md').write_text(part_b) // after — single plan document (docs/plans / 'plan.md').write_text(part_a + '\n' + part_b)
Defensive patterns
Strategy: validation
Validate before calling
from pathlib import Path
import re
def exactly_one_plan_changed(before, after) -> bool:
changed = [p for p, d in after.items() if before.get(p) != d]
return len(changed) == 1
def is_acceptable_plan_path(path: Path) -> bool:
return path.suffix.lower() in {'.md', '.html'} and path.lstat().st_mode and 0o170000 == 0o100000 Type guard
null
Try / catch
try:
plan = new_plan_doc(worktree, before)
except ValueError as e:
if 'exactly one plan artifact' in str(e):
print('planning produced', e) # inspect 0 vs N, adjust prompt/paths
raise
raise Prevention
- Write the single plan under docs/plans/ with a .md or .html suffix.
- Do not scatter planning output across multiple files or directories.
- Snapshot before/after yourself first to confirm exactly one path differs.
- Ensure the plan is a regular file (not a symlink) so snapshot_plan_docs includes it.
When it happens
Trigger: `changed = sorted(path for path, digest in after.items() if before.get(path) != digest)` has length != 1. Either no plan was created/modified, or several were.
Common situations: Agent wrote plans to a directory other than docs/plans (so zero observed); agent emitted multiple plan files; agent only touched non-plan files; the plan landed as .txt instead of .md/.html and was filtered out by snapshot_plan_docs.
Related errors
- planning deleted existing plan artifact(s): {deleted}
- {label} is unavailable: {path}: {exc}
- {label} must be a real non-symlink directory: {path}
- {label} contains an unsafe path component: {relative}
- {label} is unreadable: {path}: {exc}
AI-assisted analysis of abhigyanpatwari/GitNexus@d540b00184 (2026-08-12).
Data as JSON: /api/errors/b2834ffa9fdc54fb.
Report an issue: GitHub.