{"record":{"id":"82f3bdd55c45ba96","repo":"nexu-io/open-design","slug":"unsupported-avatar-key-for-participant-speaker","errorCode":null,"errorMessage":"Unsupported avatar key for participant {speaker}: {avatar_key}","messagePattern":"Unsupported avatar key for participant (.+?): (.+?)","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"skills/chat-motion-overlay/scripts/build_chat_overlay_spec.py","lineNumber":182,"sourceCode":"    meta = parsed[\"metadata\"]\n    participants = {}\n    used_participant_ids = set()\n    order = []\n    messages = []\n    start = int(meta[\"start\"])\n    gap = int(meta[\"gap\"])\n    for index, message in enumerate(parsed[\"messages\"]):\n        speaker = message[\"speaker\"]\n        if speaker not in participants:\n            order.append(speaker)\n            configured = configured_participant(speaker, config)\n            inferred_side = \"left\" if len(order) == 1 else \"right\" if len(order) == 2 else \"left\"\n            side = configured.get(\"side\") or message[\"side\"] or inferred_side\n            avatar_key = configured.get(\"preset\") or message[\"avatar\"] or auto_avatar_for_participant(\n                len(order) - 1, {p[\"avatarKey\"] for p in participants.values()}\n            )\n            if avatar_key not in PRESET_KEYS:\n                raise ValueError(f\"Unsupported avatar key for participant {speaker}: {avatar_key}\")\n            participant = {\n                \"id\": unique_slug(slugify(speaker), used_participant_ids),\n                \"name\": speaker,\n                \"side\": side,\n                \"avatarKey\": avatar_key,\n            }\n            used_participant_ids.add(participant[\"id\"])\n            if config[\"avatarMode\"] in {\"upload\", \"mixed\"} and configured.get(\"uploadPath\"):\n                participant[\"uploadPath\"] = configured[\"uploadPath\"]\n            if config[\"avatarMode\"] == \"upload\" and not participant.get(\"uploadPath\"):\n                raise ValueError(f\"avatarMode=upload requires uploadPath for participant {speaker}\")\n            participants[speaker] = participant\n        participant = participants[speaker]\n        side = message[\"side\"] or participant[\"side\"]\n        if side != participant[\"side\"]:\n            raise ValueError(f\"Participant {speaker} appears on both {participant['side']} and {side}; use one side per participant\")\n        avatar_key = participant[\"avatarKey\"]\n        if message[\"avatar\"] and message[\"avatar\"] != participant[\"avatarKey\"] and not configured_participant(speaker, config).get(\"preset\"):","sourceCodeStart":164,"sourceCodeEnd":200,"githubUrl":"https://github.com/nexu-io/open-design/blob/5be4028344c2eb4c667c5a97bda8f750c5597ef7/skills/chat-motion-overlay/scripts/build_chat_overlay_spec.py#L164-L200","documentation":"Thrown in build_spec() when the resolved avatar_key for a new speaker is not one of the six PRESET_KEYS. The key is resolved in priority order: config participant 'preset', then the transcript message's inline avatar hint, then auto assignment from PRESET_KEYS. If the chosen value is not in the preset library, building aborts.","triggerScenarios":"A transcript line uses the 3-or-4 pipe form whose avatar field is a non-preset token (e.g. `Alice|left|not-a-preset|hi`), and the participant has no config 'preset' override. The run_test_matrix.py case 'invalid_transcript_avatar_key' reproduces this.","commonSituations":"Typing a free-form avatar name in the transcript instead of a preset key; using an old/wrong preset name after the PRESET_KEYS list changed; leaving an avatar hint that was meant to be a filename.","solutions":["Use one of the valid preset keys: female-bunny-pink, female-cat-orange, female-fox-yellow, male-bear-mint, male-penguin-blue, male-koala-lilac.","Set a 'preset' under the participant's config entry so the transcript hint is ignored.","Omit the avatar field from the transcript line entirely so auto_avatar_for_participant assigns a valid key."],"exampleFix":"// before (transcript)\nAlice|left|not-a-preset|你好\n// after\nAlice|left|female-fox-yellow|你好","handlingStrategy":"validation","validationCode":"PRESET_KEYS = {\"female-bunny-pink\",\"female-cat-orange\",\"female-fox-yellow\",\"male-bear-mint\",\"male-penguin-blue\",\"male-koala-lilac\"}\ndef check_transcript_avatars(transcript_lines, config):\n    for line in transcript_lines:\n        parts = [p.strip() for p in line.split(\"|\")] if \"|\" in line else []\n        if not parts or len(parts) < 3:\n            continue\n        speaker = parts[0]\n        avatar = parts[-2] if len(parts) >= 4 else None\n        if avatar and avatar not in PRESET_KEYS and not config.get(\"participants\", {}).get(speaker, {}).get(\"preset\"):\n            raise ValueError(f\"invalid avatar '{avatar}' for {speaker}; set a config preset or use a preset key\")","typeGuard":"def is_preset_key(value: str) -> bool:\n    return value in {\"female-bunny-pink\",\"female-cat-orange\",\"female-fox-yellow\",\"male-bear-mint\",\"male-penguin-blue\",\"male-koala-lilac\"}","tryCatchPattern":null,"preventionTips":["Prefer setting participant 'preset' in config over inline transcript avatar hints.","Lint the transcript avatar tokens against PRESET_KEYS before invoking the builder."],"tags":["transcript-parsing","chat-motion-overlay","avatar-mode"],"backgroundTag":null,"analyzedSha":"5be4028344c2eb4c667c5a97bda8f750c5597ef7","analyzedAt":"2026-08-12T12:03:58.812Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}