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
- Set each participant's side to exactly 'left' or 'right' (lowercase English).
- To leave side unspecified (auto), omit the field or set it to null — the `if side and ...` guard only validates non-empty values.
- If you used Chinese tokens from a transcript, translate them: 左 -> left, 右 -> right.
- 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
- Use only lowercase 'left'/'right' in participant config (Chinese tokens are transcript-only).
- Omit side to leave it auto-assigned rather than guessing.
- Validate all participants in one pass before rendering.
- Document the layer difference: transcript SIDE_MAP accepts 左/右, participant config does not.
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
- Unsupported preset for participant {speaker}: {preset_key}
- Participant {speaker} config must use uploadPath, not upload
- Unsupported container: {config['container']}
- Unsupported avatarMode: {config['avatarMode']}
- Unsupported deviceFrame: {config['deviceFrame']}
AI-assisted analysis of nexu-io/open-design@5be4028344 (2026-08-12).
Data as JSON: /api/errors/a74551b1b1fa85f4.
Report an issue: GitHub.