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
- Edit the transcript so every line for that speaker uses the same side.
- If the config participant side is the intended one, remove the inline side token from the conflicting transcript lines.
- 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
- Decide each speaker's side once and reuse it for every line.
- Where possible, set side in config.participants and omit it from transcript lines.
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
- Participant {speaker} has conflicting transcript avatar hint
- Unsupported avatar key for participant {speaker}: {avatar_ke
- avatarMode=preset does not allow uploadPath for participant
- avatarMode=upload requires uploadPath for participant {speak
- avatarMode=mixed requires at least one upload path
AI-assisted analysis of nexu-io/open-design@5be4028344 (2026-08-12).
Data as JSON: /api/errors/e1398d8a1c5317c3.
Report an issue: GitHub.