nexu-io/open-design · error · ValueError

Unsupported side for participant {speaker}: {side}

Error message

Unsupported side for participant {speaker}: {side}

What it means

Raised by validate_config during participant iteration when a participant's `side` field is present and not in {'left','right'}. Note the transcript-level SIDE_MAP also accepts Chinese tokens (左/右), but the config-level participant side check is stricter: only the English 'left'/'right' strings are accepted in participant config.

Source

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

def validate_config(config: dict) -> None:
    if config["container"] not in ALLOWED_CONTAINERS:
        raise ValueError(f"Unsupported container: {config['container']}")
    if config["avatarMode"] not in ALLOWED_AVATAR_MODES:
        raise ValueError(f"Unsupported avatarMode: {config['avatarMode']}")
    if config["deviceFrame"] not in ALLOWED_DEVICE_FRAMES:
        raise ValueError(f"Unsupported deviceFrame: {config['deviceFrame']}")
    if config["nicknameMode"] not in ALLOWED_NICKNAME_MODES:
        raise ValueError(f"Unsupported nicknameMode: {config['nicknameMode']}")
    if config["deliveryFormat"] not in ALLOWED_DELIVERY_FORMATS:
        raise ValueError(f"Unsupported deliveryFormat: {config['deliveryFormat']}")
    if config["container"] == "none" and config["deviceFrame"] == "iphone-dynamic-island":
        raise ValueError("container=none does not support deviceFrame=iphone-dynamic-island; use deviceFrame=none or choose an app container")

    for speaker, participant in config.get("participants", {}).items():
        side = participant.get("side")
        if side and side not in {"left", "right"}:
            raise ValueError(f"Unsupported side for participant {speaker}: {side}")
        preset_key = participant.get("preset")
        if preset_key and preset_key not in PRESET_KEYS:
            raise ValueError(f"Unsupported preset for participant {speaker}: {preset_key}")
        upload_path = participant.get("uploadPath")
        if participant.get("uploadAsset"):
            raise ValueError(f"Participant {speaker} config must use uploadPath, not uploadAsset")
        if config["avatarMode"] == "preset" and upload_path:
            raise ValueError(f"avatarMode=preset does not allow uploadPath for participant {speaker}")
        if config["avatarMode"] == "upload" and not upload_path:
            raise ValueError(f"avatarMode=upload requires uploadPath for participant {speaker}")


def auto_avatar_for_participant(participant_index: int, used_avatar_keys: set[str]) -> str:
    preferred = [*PRESET_KEYS[participant_index:], *PRESET_KEYS[:participant_index]]
    for avatar_key in preferred:
        if avatar_key not in used_avatar_keys:
            return avatar_key
    return PRESET_KEYS[participant_index % len(PRESET_KEYS)]

View on GitHub (pinned to 5be4028344)

Solutions

  1. Set each participant's side to exactly 'left' or 'right' (lowercase English).
  2. To leave side unspecified (auto), omit the field or set it to null — the `if side and ...` guard only validates non-empty values.
  3. If you used Chinese tokens from a transcript, translate them: 左 -> left, 右 -> right.
  4. Pre-validate: `for p in c.get('participants',{}).values(): assert not p.get('side') or p['side'] in {'left','right'}`.

Example fix

// before
{"participants": {"Alice": {"side": "l"}}}
# -> ValueError: Unsupported side for participant Alice: l

// after
{"participants": {"Alice": {"side": "left"}}}
Defensive patterns

Strategy: validation

Validate before calling

for speaker, participant in config.get("participants", {}).items():
    side = participant.get("side")
    if side and side not in {"left", "right"}:
        raise SystemExit(
            f"Participant {speaker} side must be 'left' or 'right'; got {side!r}. "
            "(Omit side to leave it auto.)"
        )

Type guard

def is_participant_side(value) -> bool:
    return not value or value in {"left", "right"}

Try / catch

try:
    validate_config(config)
except ValueError as exc:
    raise SystemExit(f"Config error: {exc}") from exc

Prevention

When it happens

Trigger: User config defines `"participants": {"Alice": {"side": "l"}}`, or `{"side": "top"}`, `{"side": "left-side"}`, `{"side": "左"}` (Chinese token not accepted at config layer), or a typo. Any truthy non-{left,right} value triggers it; null/missing side is allowed (the `if side and ...` guard skips empty).

Common situations: Abbreviating side ('l'/'r'); using the Chinese token that works in transcripts but not in config; wanting a centered speaker; copy-paste introducing extra text; boolean or numeric side.

Related errors


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