{"record":{"id":"b9258838eee7f162","repo":"nexu-io/open-design","slug":"participant-speaker-config-must-use-uploadpath","errorCode":null,"errorMessage":"Participant {speaker} config must use uploadPath, not uploadAsset","messagePattern":"Participant (.+?) config must use uploadPath, not uploadAsset","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"skills/chat-motion-overlay/scripts/build_chat_overlay_spec.py","lineNumber":144,"sourceCode":"    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:\n    return config.get(\"participants\", {}).get(speaker, {})\n\n","sourceCodeStart":126,"sourceCodeEnd":162,"githubUrl":"https://github.com/nexu-io/open-design/blob/5be4028344c2eb4c667c5a97bda8f750c5597ef7/skills/chat-motion-overlay/scripts/build_chat_overlay_spec.py#L126-L162","documentation":"Raised by validate_config during participant iteration when a participant config contains an `uploadAsset` key. The script intentionally rejects `uploadAsset` and requires the semantically correct `uploadPath` field instead. The guard fires regardless of avatarMode whenever uploadAsset is truthy, catching a renamed/deprecated field name.","triggerScenarios":"User copies an old or external config that used `uploadAsset` (perhaps from an earlier API or a different tool), or guesses the field name from related 'asset' terminology in the codebase. Any truthy uploadAsset value triggers the error.","commonSituations":"Field rename from an earlier version where uploadAsset was valid; confusion with other parts of the system that use an 'asset' concept; documentation/example using the old name; auto-generated config emitting the wrong key.","solutions":["Rename the field from `uploadAsset` to `uploadPath` in the participant config, keeping the same value (an absolute path to the avatar image).","Ensure avatarMode is compatible: 'upload' requires uploadPath; 'preset' disallows it (see related errors).","Search the config source for any lingering 'uploadAsset' references and replace them all.","If generating config programmatically, update the schema/serialization to emit uploadPath."],"exampleFix":"// before\n{\"avatarMode\": \"upload\", \"participants\": {\"Alice\": {\"uploadAsset\": \"/abs/avatar.png\"}}}\n# -> ValueError: Participant Alice config must use uploadPath, not uploadAsset\n\n// after\n{\"avatarMode\": \"upload\", \"participants\": {\"Alice\": {\"uploadPath\": \"/abs/avatar.png\"}}}","handlingStrategy":"validation","validationCode":"for speaker, participant in config.get(\"participants\", {}).items():\n    if participant.get(\"uploadAsset\"):\n        raise SystemExit(\n            f\"Participant {speaker} uses uploadAsset, which is not supported. \"\n            \"Rename it to uploadPath (same value).\"\n        )","typeGuard":null,"tryCatchPattern":"try:\n    validate_config(config)\nexcept ValueError as exc:\n    raise SystemExit(f\"Config error: {exc}\") from exc","preventionTips":["Use 'uploadPath' (the current field name), never 'uploadAsset'.","Migrate any legacy config files that still use uploadAsset.","If generating config from a schema, update the schema/serialization to emit uploadPath.","Add a one-time migration check that scans configs for deprecated field names."],"tags":["config","validation","chat-overlay","field-rename","participants"],"backgroundTag":null,"analyzedSha":"5be4028344c2eb4c667c5a97bda8f750c5597ef7","analyzedAt":"2026-08-12T12:03:58.812Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}