{"record":{"id":"3318d75fbf95a948","repo":"nexu-io/open-design","slug":"participant-speaker-has-conflicting-transcript-a","errorCode":null,"errorMessage":"Participant {speaker} has conflicting transcript avatar hints: {participant['avatarKey']} and {message['avatar']}; set a config preset to override","messagePattern":"Participant (.+?) has conflicting transcript avatar hints: (.+?) and (.+?); set a config preset to override","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"skills/chat-motion-overlay/scripts/build_chat_overlay_spec.py","lineNumber":201,"sourceCode":"            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\"):\n            raise ValueError(f\"Participant {speaker} has conflicting transcript avatar hints: {participant['avatarKey']} and {message['avatar']}; set a config preset to override\")\n        messages.append(\n            {\n                \"id\": f\"msg-{index + 1}\",\n                \"speaker\": speaker,\n                \"text\": message[\"text\"].strip(),\n                \"side\": side,\n                \"avatarKey\": avatar_key,\n                \"appearAt\": start + index * gap,\n                \"highlight\": bool(message[\"highlight\"]),\n            }\n        )\n    if config[\"avatarMode\"] == \"mixed\" and not any(participant.get(\"uploadPath\") for participant in participants.values()):\n        raise ValueError(\"avatarMode=mixed requires at least one upload path\")\n    duration = start + max(len(messages) - 1, 0) * gap + int(meta[\"hold\"])\n    runtime_output = DELIVERY_TO_OUTPUT[config[\"deliveryFormat\"]]\n    scene_config = {key: value for key, value in config.items() if key != \"participants\"}\n    return {\n        \"title\": meta[\"title\"],","sourceCodeStart":183,"sourceCodeEnd":219,"githubUrl":"https://github.com/nexu-io/open-design/blob/5be4028344c2eb4c667c5a97bda8f750c5597ef7/skills/chat-motion-overlay/scripts/build_chat_overlay_spec.py#L183-L219","documentation":"Thrown in build_spec() when a speaker has multiple transcript lines whose inline avatar hints disagree, and no config 'preset' override exists to pin a single avatar. Without a config preset the builder cannot know which hint wins, so it refuses rather than silently picking one.","triggerScenarios":"Two transcript lines for the same speaker use different avatar tokens in the avatar slot (e.g. `Bob|left|female-fox-yellow|hi` and `Bob|left|male-bear-mint|yo`), and config.participants has no 'preset' for that speaker.","commonSituations":"Copy-pasting message lines from different templates; partially editing avatar hints; intending per-message avatar variety (which the spec does not support without a config preset).","solutions":["Set a single 'preset' under config.participants for that speaker; config preset wins and transcript hints are ignored.","Make all transcript avatar hints for that speaker identical and a valid PRESET_KEYS value.","Remove the avatar field from the conflicting transcript lines so the auto-assignment path is used."],"exampleFix":"// before\n// transcript:\nBob|left|female-fox-yellow|hi\nBob|left|male-bear-mint|yo\n// config has no Bob preset -> after (config)\n{\"participants\": {\"Bob\": {\"side\": \"left\", \"preset\": \"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_avatar_hint_consistency(transcript_path, config):\n    configured = {n for n, p in config.get(\"participants\", {}).items() if p.get(\"preset\")}\n    hints = {}\n    for raw in transcript_path.read_text(encoding=\"utf-8\").splitlines():\n        parts = [p.strip() for p in raw.split(\"|\")] if \"|\" in raw else []\n        if len(parts) < 4:\n            continue\n        avatar, speaker = parts[-2], parts[0]\n        if avatar not in PRESET_KEYS or speaker in configured:\n            continue\n        if speaker in hints and hints[speaker] != avatar:\n            raise ValueError(f\"{speaker} has conflicting avatar hints; set a config preset\")\n        hints[speaker] = avatar","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Pin a single avatar per speaker via config 'preset' when in doubt.","Remove inline avatar hints once a config preset is set."],"tags":["transcript-parsing","chat-motion-overlay","participant-config"],"backgroundTag":null,"analyzedSha":"5be4028344c2eb4c667c5a97bda8f750c5597ef7","analyzedAt":"2026-08-12T12:03:58.812Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}