nexu-io/open-design · error · SystemExit

running-left is not configured for conditional mirroring

Error message

running-left is not configured for conditional mirroring

What it means

Mirroring is opt-in per pet. The running-left job in imagegen-jobs.json must carry a mirror_policy object whose may_derive_from equals the literal "running-right". Without this declaration the script treats mirroring as unapproved for this pet, because some pets are asymmetric (props, markings, handedness) and must be drawn independently.

Solutions

  1. Visually confirm the pet is actually symmetric enough that mirroring running-right produces a correct left strip.
  2. If symmetric, add "mirror_policy": {"may_derive_from": "running-right"} to the running-left job in imagegen-jobs.json.
  3. If asymmetric, abandon mirroring and generate running-left independently via $imagegen + record_imagegen_result.py.
  4. Re-run the derive script.

Example fix

// before (running-left job)
{
  "id": "running-left",
  "status": "pending"
}
// after
{
  "id": "running-left",
  "status": "pending",
  "mirror_policy": { "may_derive_from": "running-right" }
}
Defensive patterns

Strategy: type-guard

Validate before calling

import json
from pathlib import Path

run_dir = Path("<run_dir>")
manifest = json.loads((run_dir / "imagegen-jobs.json").read_text())
left = next(j for j in manifest["jobs"] if j.get("id") == "running-left")
policy = left.get("mirror_policy")
assert isinstance(policy, dict) and policy.get("may_derive_from") == "running-right", \
    "running-left mirror_policy.may_derive_from must be 'running-right'"

Type guard

def mirror_policy_ok(left_job: dict) -> bool:
    policy = left_job.get("mirror_policy")
    return isinstance(policy, dict) and policy.get("may_derive_from") == "running-right"

Prevention

When it happens

Trigger: The running-left job has no mirror_policy key, mirror_policy is not a dict, or mirror_policy["may_derive_from"] is set to a value other than "running-right".

Common situations: A pet run created before mirror_policy was introduced; a manifest hand-edited to strip the policy; an asymmetric pet that was never meant to mirror but someone is trying to shortcut it.

Related errors


AI-assisted analysis of nexu-io/open-design@5be4028344 (2026-08-12). Data as JSON: /api/errors/24c2d05ee326bd18. Report an issue: GitHub.

Appendix: source

Thrown at skills/hatch-pet/scripts/derive_running_left_from_running_right.py:91

    parser.add_argument("--force", action="store_true")
    args = parser.parse_args()

    if not args.confirm_appropriate_mirror:
        raise SystemExit("refusing to mirror without --confirm-appropriate-mirror")
    if not args.decision_note.strip():
        raise SystemExit("--decision-note must explain why mirroring is appropriate")

    run_dir = Path(args.run_dir).expanduser().resolve()
    manifest_path = run_dir / "imagegen-jobs.json"
    manifest = load_manifest(run_dir)
    right_job = find_job(manifest, "running-right")
    left_job = find_job(manifest, "running-left")

    if right_job.get("status") != "complete":
        raise SystemExit("running-right must be complete before deriving running-left")
    mirror_policy = left_job.get("mirror_policy")
    if not isinstance(mirror_policy, dict) or mirror_policy.get("may_derive_from") != "running-right":
        raise SystemExit("running-left is not configured for conditional mirroring")

    source = run_dir / "decoded" / "running-right.png"
    output = run_dir / "decoded" / "running-left.png"
    if not source.is_file():
        raise SystemExit(f"running-right decoded strip not found: {source}")
    if output.exists() and not args.force:
        raise SystemExit(f"{output} already exists; pass --force to replace it")

    output.parent.mkdir(parents=True, exist_ok=True)
    with Image.open(source) as image:
        mirrored = ImageOps.mirror(image.convert("RGBA"))
        mirrored.save(output)

    left_job["status"] = "complete"
    left_job["source_path"] = manifest_relative(source, run_dir)
    left_job["source_provenance"] = "deterministic-mirror"
    left_job["derived_from"] = "running-right"
    left_job["source_sha256"] = file_sha256(source)

View on GitHub (pinned to 5be4028344)