twentyhq/twenty · error · ValueError

Duplicate key found in JSON: '{key}'

Error message

Duplicate key found in JSON: '{key}'

What it means

Raised by the object_pairs_hook `check_duplicate_keys` passed to json.load when parsing the replacement JSON in replace.py. Because Python's default dict silently overwrites duplicate keys, the hook makes a duplicate key a hard error so a replacement file cannot accidentally shadow an earlier mapping for the same shape/slide.

Source

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

                                first_text += "..."
                            unused_with_content.append(f"{k} ('{first_text}')")
                        else:
                            unused_with_content.append(k)

                errors.append(
                    f"Shape '{shape_key}' not found on '{slide_key}'. "
                    f"Shapes without replacements: {', '.join(sorted(unused_with_content)) if unused_with_content else 'none'}"
                )

    return errors


def check_duplicate_keys(pairs):
    """Check for duplicate keys when loading JSON."""
    result = {}
    for key, value in pairs:
        if key in result:
            raise ValueError(f"Duplicate key found in JSON: '{key}'")
        result[key] = value
    return result


def apply_replacements(pptx_file: str, json_file: str, output_file: str):
    """Apply text replacements from JSON to PowerPoint presentation."""

    # Load presentation
    prs = Presentation(pptx_file)

    # Get inventory of all text shapes (returns ShapeData objects)
    # Pass prs to use same Presentation instance
    inventory = extract_text_inventory(Path(pptx_file), prs)

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

    # Load replacement data with duplicate key detection

View on GitHub (pinned to 1f5dd2bbd2)

Solutions

  1. Open the replacement JSON and find the duplicated key named in the message; merge or rename the conflicting entry.
  2. Validate the JSON with a linter that flags duplicate keys (e.g. `python -c` using the same object_pairs_hook) before passing it to apply_replacements.
  3. When generating replacements programmatically, accumulate into a dict so later writes overwrite deterministically, then serialize once.

Example fix

// before — replacement.json has two "slide-1" keys
{
  "slide-1": { "title": "A" },
  "slide-1": { "subtitle": "B" }
}
// after — merged under one key
{
  "slide-1": { "title": "A", "subtitle": "B" }
}
Defensive patterns

Strategy: validation

Validate before calling

import json

def load_replacements_strict(path: str) -> dict:
    with open(path) as f:
        return json.load(f, object_pairs_hook=check_duplicate_keys)

# Pre-flight: lint the file before the tool runs
try:
    load_replacements_strict('replacements.json')
except ValueError as e:
    print(f'Fix duplicate keys first: {e}')
    raise

Type guard

def has_no_duplicate_keys(pairs) -> bool:
    seen = set()
    for k, _ in pairs:
        if k in seen:
            return False
        seen.add(k)
    return True

Try / catch

try:
    replacements = json.load(f, object_pairs_hook=check_duplicate_keys)
except ValueError as e:
    if 'Duplicate key' in str(e):
        # surface the key name and the file path to the operator, then abort
        raise ValueError(f'{path}: {e}. Merge or rename the duplicate before retrying.') from e
    raise

Prevention

When it happens

Trigger: The replacement JSON contains the same key twice at the same nesting level — e.g. two `slide-1` entries, or two shape keys under the same slide. This typically comes from hand-editing or from an LLM concatenating partial replacement blocks without dedup.

Common situations: An LLM in the code-interpreter merges two inventory-driven replacement drafts into one JSON and forgets to dedupe; a templating step emits a key per source without grouping; copy-paste of a shape block that retains the original key.

Related errors


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