{"record":{"id":"c74497948262e09f","repo":"nexu-io/open-design","slug":"unsupported-preset-for-participant-speaker-pre","errorCode":null,"errorMessage":"Unsupported preset for participant {speaker}: {preset_key}","messagePattern":"Unsupported preset for participant (.+?): (.+?)","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"skills/chat-motion-overlay/scripts/build_chat_overlay_spec.py","lineNumber":141,"sourceCode":"        raise ValueError(f\"Unsupported container: {config['container']}\")\n    if config[\"avatarMode\"] not in ALLOWED_AVATAR_MODES:\n        raise ValueError(f\"Unsupported avatarMode: {config['avatarMode']}\")\n    if config[\"deviceFrame\"] not in ALLOWED_DEVICE_FRAMES:\n        raise ValueError(f\"Unsupported deviceFrame: {config['deviceFrame']}\")\n    if config[\"nicknameMode\"] not in ALLOWED_NICKNAME_MODES:\n        raise ValueError(f\"Unsupported nicknameMode: {config['nicknameMode']}\")\n    if config[\"deliveryFormat\"] not in ALLOWED_DELIVERY_FORMATS:\n        raise ValueError(f\"Unsupported deliveryFormat: {config['deliveryFormat']}\")\n    if config[\"container\"] == \"none\" and config[\"deviceFrame\"] == \"iphone-dynamic-island\":\n        raise ValueError(\"container=none does not support deviceFrame=iphone-dynamic-island; use deviceFrame=none or choose an app container\")\n\n    for speaker, participant in config.get(\"participants\", {}).items():\n        side = participant.get(\"side\")\n        if side and side not in {\"left\", \"right\"}:\n            raise ValueError(f\"Unsupported side for participant {speaker}: {side}\")\n        preset_key = participant.get(\"preset\")\n        if preset_key and preset_key not in PRESET_KEYS:\n            raise ValueError(f\"Unsupported preset for participant {speaker}: {preset_key}\")\n        upload_path = participant.get(\"uploadPath\")\n        if participant.get(\"uploadAsset\"):\n            raise ValueError(f\"Participant {speaker} config must use uploadPath, not uploadAsset\")\n        if config[\"avatarMode\"] == \"preset\" and upload_path:\n            raise ValueError(f\"avatarMode=preset does not allow uploadPath for participant {speaker}\")\n        if config[\"avatarMode\"] == \"upload\" and not upload_path:\n            raise ValueError(f\"avatarMode=upload requires uploadPath for participant {speaker}\")\n\n\ndef auto_avatar_for_participant(participant_index: int, used_avatar_keys: set[str]) -> str:\n    preferred = [*PRESET_KEYS[participant_index:], *PRESET_KEYS[:participant_index]]\n    for avatar_key in preferred:\n        if avatar_key not in used_avatar_keys:\n            return avatar_key\n    return PRESET_KEYS[participant_index % len(PRESET_KEYS)]\n\n\ndef configured_participant(speaker: str, config: dict) -> dict:","sourceCodeStart":123,"sourceCodeEnd":159,"githubUrl":"https://github.com/nexu-io/open-design/blob/5be4028344c2eb4c667c5a97bda8f750c5597ef7/skills/chat-motion-overlay/scripts/build_chat_overlay_spec.py#L123-L159","documentation":"Raised by validate_config during participant iteration when a participant's `preset` field is present and not one of PRESET_KEYS = [female-bunny-pink, female-cat-orange, female-fox-yellow, male-bear-mint, male-penguin-blue, male-koala-lilac]. The check is skipped if preset is omitted (avatarMode='upload' participants need no preset), but any non-empty unrecognized preset string triggers it.","triggerScenarios":"User sets `\"preset\": \"cat\"`, `\"preset\": \"alice\"`, `\"preset\": \"female-bunny\"` (truncated), or invents a name not in the six-key roster. Also fires on typos and case mismatches ('Female-Bunny-Pink').","commonSituations":"Wanting a custom avatar and setting a preset key for it (custom avatars go through uploadPath + avatarMode='upload', not preset); partial copy of a key; case sensitivity; outdated preset list from a previous version.","solutions":["Use one of the exact preset keys: female-bunny-pink, female-cat-orange, female-fox-yellow, male-bear-mint, male-penguin-blue, male-koala-lilac.","For a custom avatar, switch avatarMode to 'upload' and supply uploadPath instead of preset.","Lowercase exact match required; verify spelling against PRESET_KEYS in the script.","Pre-validate: `PRESET={'female-bunny-pink',...}; for p in c.get('participants',{}).values(): assert not p.get('preset') or p['preset'] in PRESET`."],"exampleFix":"// before\n{\"avatarMode\": \"preset\", \"participants\": {\"Alice\": {\"preset\": \"cat\"}}}\n# -> ValueError: Unsupported preset for participant Alice: cat\n\n// after\n{\"avatarMode\": \"preset\", \"participants\": {\"Alice\": {\"preset\": \"female-cat-orange\"}}}","handlingStrategy":"validation","validationCode":"PRESET_KEYS = {\n    \"female-bunny-pink\", \"female-cat-orange\", \"female-fox-yellow\",\n    \"male-bear-mint\", \"male-penguin-blue\", \"male-koala-lilac\",\n}\n\nfor speaker, participant in config.get(\"participants\", {}).items():\n    preset = participant.get(\"preset\")\n    if preset and preset not in PRESET_KEYS:\n        raise SystemExit(\n            f\"Participant {speaker} preset must be one of {sorted(PRESET_KEYS)}; \"\n            f\"got {preset!r}. For custom avatars use avatarMode='upload' + uploadPath.\"\n        )","typeGuard":"def is_preset_key(value) -> bool:\n    return value in {\n        \"female-bunny-pink\", \"female-cat-orange\", \"female-fox-yellow\",\n        \"male-bear-mint\", \"male-penguin-blue\", \"male-koala-lilac\",\n    }","tryCatchPattern":"try:\n    validate_config(config)\nexcept ValueError as exc:\n    raise SystemExit(f\"Config error: {exc}\") from exc","preventionTips":["Use exact preset keys from PRESET_KEYS (lowercase, hyphenated).","For custom avatars, use avatarMode='upload' with uploadPath rather than inventing presets.","Validate presets in one pass with the other participant checks.","Regenerate docs/help from PRESET_KEYS so the list stays accurate."],"tags":["config","validation","chat-overlay","avatar","participants"],"backgroundTag":null,"analyzedSha":"5be4028344c2eb4c667c5a97bda8f750c5597ef7","analyzedAt":"2026-08-12T12:03:58.812Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}