abhigyanpatwari/GitNexus · error · ValueError

planning deleted existing plan artifact(s): {deleted}

Error message

planning deleted existing plan artifact(s): {deleted}

What it means

Thrown by new_plan_doc when comparing the before-snapshot to the after-snapshot of plan artifacts. If any path present in the before-dict is absent from the after-dict, the planning phase deleted a previously existing plan markdown/html file. The benchmark requires planning to be additive, so destructive behavior is rejected as ambiguous evidence.

Source

Thrown at eval/workflow_bench/runner_artifacts.py:293

            if not stat.S_ISREG(opened.st_mode) or opened.st_dev != metadata.st_dev or opened.st_ino != metadata.st_ino:
                raise ValueError(f"plan artifact changed while opening: {path}")
            with os.fdopen(descriptor, "rb", closefd=False) as handle:
                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",

View on GitHub (pinned to d540b00184)

Solutions

  1. Make the planning step only create or modify plans, never delete existing ones.
  2. If a stale plan must be replaced, overwrite it in place rather than unlink + create.
  3. Audit agent prompts/tooling for any `rm`/`git clean` touching docs/plans.
  4. Pre-populate the worktree with no pre-existing plans if the task does not require editing one, so there is nothing to delete.

Example fix

// before — agent deletes then writes
os.remove(plan_path)
plan_path.write_text(new_plan)

// after — overwrite in place, never delete
plan_path.write_text(new_plan)
Defensive patterns

Strategy: validation

Validate before calling

from pathlib import Path

def plans_only_added(before: dict[Path, str], after: dict[Path, str]) -> bool:
    return all(p in after for p in before)

Type guard

null

Try / catch

try:
    new_plan_doc(worktree, before)
except ValueError as e:
    if 'deleted existing plan artifact' in str(e):
        # planning was destructive; fix the agent/prompt to overwrite, not delete
        raise
    raise

Prevention

When it happens

Trigger: In new_plan_doc, `deleted = sorted(path for path in before if path not in after)` is non-empty — a plan file that existed before the planning phase no longer exists after it.

Common situations: An agent removes a stale plan before writing its own; a git checkout/clean inside the agent step deletes docs/plans; a setup hook that tidies the plans dir; the agent treats docs/plans as scratch space.

Related errors


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