twentyhq/twenty · error · ValueError

Found {len(errors)} validation error(s)

Error message

Found {len(errors)} validation error(s)

What it means

Raised by apply_replacements in replace.py after validate_replacements returns a non-empty error list. validate_replacements checks every shape referenced in the replacement JSON against the inventory of actual text shapes on the deck; any shape_key or slide_key that does not exist, or shapes on the slide that were left without a replacement when the file intended full coverage, becomes an error. The throw halts before any slide is mutated.

Source

Thrown at packages/twenty-server/src/engine/core-modules/code-interpreter/sandbox-scripts/pptx/replace.py:241

    # Detect text overflow in original presentation
    original_overflow = detect_frame_overflow(inventory)

    # Load replacement data with duplicate key detection
    with open(json_file, "r") as f:
        replacements = json.load(f, object_pairs_hook=check_duplicate_keys)

    # Validate replacements
    errors = validate_replacements(inventory, replacements)
    if errors:
        print("ERROR: Invalid shapes in replacement JSON:")
        for error in errors:
            print(f"  - {error}")
        print("\nPlease check the inventory and update your replacement JSON.")
        print(
            "You can regenerate the inventory with: python inventory.py <input.pptx> <output.json>"
        )
        raise ValueError(f"Found {len(errors)} validation error(s)")

    # Track statistics
    shapes_processed = 0
    shapes_cleared = 0
    shapes_replaced = 0

    # Process each slide from inventory
    for slide_key, shapes_dict in inventory.items():
        if not slide_key.startswith("slide-"):
            continue

        slide_index = int(slide_key.split("-")[1])

        if slide_index >= len(prs.slides):
            print(f"Warning: Slide {slide_index} not found")
            continue

        # Process each shape from inventory

View on GitHub (pinned to 1f5dd2bbd2)

Solutions

  1. Read the printed error list — each line names the offending slide/shape and what was expected.
  2. Regenerate the inventory with `python inventory.py <input.pptx> <inventory.json>` and align your replacement keys to the current shape names.
  3. If you only want to replace some shapes, ensure your replacement file's intent matches what validate_replacements expects (see its unused-shape handling).
  4. Re-run apply_replacements once every reported shape key exists in the inventory.

Example fix

# before — replacement references shape 'Subtitle' that no longer exists
python replace.py deck.pptx bad_replacements.json out.pptx
# after — regenerate inventory, then rebuild replacements against real shape names
python inventory.py deck.pptx inventory.json
# edit replacements.json to use the keys inventory.json lists, then:
python replace.py deck.pptx replacements.json out.pptx
Defensive patterns

Strategy: validation

Validate before calling

import json
from inventory import build_inventory

def prevalidate_replacements(pptx_path: str, replacements_path: str) -> list[str]:
    inventory = build_inventory(pptx_path)
    with open(replacements_path) as f:
        replacements = json.load(f, object_pairs_hook=check_duplicate_keys)
    return validate_replacements(inventory, replacements)  # returns [] when clean

Type guard

def replacements_are_clean(inventory: dict, replacements: dict) -> bool:
    return not validate_replacements(inventory, replacements)

Try / catch

try:
    apply_replacements(pptx, replacements_json, output)
except ValueError as e:
    if 'validation error' in str(e):
        # errors already printed to stdout; regenerate inventory and prompt for a fixed file
        run_inventory(pptx, 'inventory.json')
        raise SystemExit('Replacements did not match inventory. See errors above; inventory.json regenerated.')
    raise

Prevention

When it happens

Trigger: The replacement JSON references a shape name that does not exist on the named slide, references a slide key (slide-N) whose N is out of range, or — when the replacement file was meant to cover all content shapes — leaves shapes un-replaced. The detailed per-error list is printed to stdout just before the throw.

Common situations: An LLM edits an old replacement JSON after the deck changed (shapes renamed/reordered); a replacement file built from one deck is reused on another; typos in shape keys; a partial replacement intended for a few shapes but run in strict-full-coverage mode.

Related errors


AI-assisted analysis of twentyhq/twenty@1f5dd2bbd2 (2026-08-12). Data as JSON: /api/errors/19f4ef11a514c4c8. Report an issue: GitHub.