nexu-io/open-design · error · ValueError

Participant {speaker} appears on both {participant['side']}

Error message

Participant {speaker} appears on both {participant['side']} and {side}; use one side per participant

What it means

Thrown in build_spec() when a speaker's message declares a side that differs from the side already stored on that participant. The overlay requires each participant to stay on one side (left or right) for the whole conversation; flipping sides mid-conversation is treated as a data error.

Source

Thrown at skills/chat-motion-overlay/scripts/build_chat_overlay_spec.py:198

            )
            if avatar_key not in PRESET_KEYS:
                raise ValueError(f"Unsupported avatar key for participant {speaker}: {avatar_key}")
            participant = {
                "id": unique_slug(slugify(speaker), used_participant_ids),
                "name": speaker,
                "side": side,
                "avatarKey": avatar_key,
            }
            used_participant_ids.add(participant["id"])
            if config["avatarMode"] in {"upload", "mixed"} and configured.get("uploadPath"):
                participant["uploadPath"] = configured["uploadPath"]
            if config["avatarMode"] == "upload" and not participant.get("uploadPath"):
                raise ValueError(f"avatarMode=upload requires uploadPath for participant {speaker}")
            participants[speaker] = participant
        participant = participants[speaker]
        side = message["side"] or participant["side"]
        if side != participant["side"]:
            raise ValueError(f"Participant {speaker} appears on both {participant['side']} and {side}; use one side per participant")
        avatar_key = participant["avatarKey"]
        if message["avatar"] and message["avatar"] != participant["avatarKey"] and not configured_participant(speaker, config).get("preset"):
            raise ValueError(f"Participant {speaker} has conflicting transcript avatar hints: {participant['avatarKey']} and {message['avatar']}; set a config preset to override")
        messages.append(
            {
                "id": f"msg-{index + 1}",
                "speaker": speaker,
                "text": message["text"].strip(),
                "side": side,
                "avatarKey": avatar_key,
                "appearAt": start + index * gap,
                "highlight": bool(message["highlight"]),
            }
        )
    if config["avatarMode"] == "mixed" and not any(participant.get("uploadPath") for participant in participants.values()):
        raise ValueError("avatarMode=mixed requires at least one upload path")
    duration = start + max(len(messages) - 1, 0) * gap + int(meta["hold"])
    runtime_output = DELIVERY_TO_OUTPUT[config["deliveryFormat"]]

View on GitHub (pinned to 5be4028344)

Solutions

  1. Edit the transcript so every line for that speaker uses the same side.
  2. If the config participant side is the intended one, remove the inline side token from the conflicting transcript lines.
  3. For genuinely different speakers that happen to share a name, rename one so each maps to a distinct participant.

Example fix

// before (transcript)
老婆|right|第一句
老婆|left|第二句
// after
老婆|right|第一句
老婆|right|第二句
Defensive patterns

Strategy: validation

Validate before calling

SIDE_TOKENS = {"left","right"}
def check_side_consistency(transcript_path, config):
    seen = {name: p.get("side") for name, p in config.get("participants", {}).items() if p.get("side")}
    for raw in transcript_path.read_text(encoding="utf-8").splitlines():
        parts = [p.strip() for p in raw.split("|")] if "|" in raw else []
        if len(parts) < 3 or parts[1] not in SIDE_TOKENS:
            continue
        speaker, side = parts[0], parts[1]
        if speaker in seen and seen[speaker] != side:
            raise ValueError(f"{speaker} appears on both {seen[speaker]} and {side}")
        seen[speaker] = side

Prevention

When it happens

Trigger: The same speaker appears in two transcript lines with conflicting inline side hints (e.g. `老婆|right|...` then `老婆|left|...`), or a transcript line side hint disagrees with the side set in config.participants for that speaker. The run_test_matrix.py case 'invalid_participant_side_conflict' reproduces this.

Common situations: Hand-editing a transcript and getting the side token wrong on one line; copying a message block from another conversation with opposite left/right convention; a config participant side that contradicts the transcript.

Related errors


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