nexu-io/open-design · error · ValueError

avatarMode=preset does not allow uploadPath for participant

Error message

avatarMode=preset does not allow uploadPath for participant {speaker}

What it means

Thrown by validate_config() in build_chat_overlay_spec.py when avatarMode is 'preset' but a participant also supplies an 'uploadPath'. Preset mode draws avatars exclusively from the built-in PRESET_KEYS library, so an upload path is contradictory. It is a config-level guard that fires before any transcript parsing or spec building.

Source

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

    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)]


def configured_participant(speaker: str, config: dict) -> dict:
    return config.get("participants", {}).get(speaker, {})


def build_spec(parsed: dict, config: dict) -> dict:
    meta = parsed["metadata"]

View on GitHub (pinned to 5be4028344)

Solutions

  1. Set "avatarMode" to "upload" or "mixed" in the config if you intend to use the uploaded avatar.
  2. Remove the "uploadPath" key from every participant entry when you want to keep avatarMode='preset'.
  3. Audit the participants dict: only 'side', 'preset', and (for upload/mixed) 'uploadPath' are honored; drop stray uploadAsset/uploadPath you did not mean to set.

Example fix

// before
{"avatarMode": "preset", "participants": {"老婆": {"side": "right", "preset": "female-cat-orange", "uploadPath": "/abs/me.png"}}}
// after
{"avatarMode": "preset", "participants": {"老婆": {"side": "right", "preset": "female-cat-orange"}}}
Defensive patterns

Strategy: validation

Validate before calling

def preset_mode_has_no_upload_paths(config: dict) -> None:
    if config.get("avatarMode") != "preset":
        return
    offenders = [name for name, p in config.get("participants", {}).items() if p.get("uploadPath")]
    if offenders:
        raise ValueError(f"avatarMode=preset forbids uploadPath; offenders: {offenders}")

preset_mode_has_no_upload_paths(config)

Prevention

When it happens

Trigger: Run build_chat_overlay_spec.py --config config.json (or prepare_chat_overlay_bundle.py, which calls load_config) where the JSON has "avatarMode": "preset" and any entry under "participants" contains an "uploadPath" key with a truthy value.

Common situations: Copying a config block from an 'upload' or 'mixed' example and forgetting to switch avatarMode; iterating on configs and leaving a stale uploadPath; misunderstanding that preset mode forbids uploads entirely rather than ignoring them.

Related errors


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