{"record":{"id":"30bd5fe31db42769","repo":"nexu-io/open-design","slug":"unsupported-avatarmode-config-avatarmode","errorCode":null,"errorMessage":"Unsupported avatarMode: {config['avatarMode']}","messagePattern":"Unsupported avatarMode: (.+?)","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"skills/chat-motion-overlay/scripts/build_chat_overlay_spec.py","lineNumber":125,"sourceCode":"    return value.strip().lower() == \"highlight\"\n\n\ndef load_config(path: str | None) -> dict:\n    config = json.loads(json.dumps(DEFAULT_CONFIG))\n    if not path:\n        validate_config(config)\n        return config\n    user = json.loads(Path(path).read_text(encoding=\"utf-8\"))\n    config.update(user)\n    validate_config(config)\n    return config\n\n\ndef validate_config(config: dict) -> None:\n    if config[\"container\"] not in ALLOWED_CONTAINERS:\n        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\"):","sourceCodeStart":107,"sourceCodeEnd":143,"githubUrl":"https://github.com/nexu-io/open-design/blob/5be4028344c2eb4c667c5a97bda8f750c5597ef7/skills/chat-motion-overlay/scripts/build_chat_overlay_spec.py#L107-L143","documentation":"Raised by validate_config when config['avatarMode'] is not in ALLOWED_AVATAR_MODES = {\"preset\",\"upload\",\"mixed\"}. Default is 'preset'. 'preset' uses a built-in avatar key per participant; 'upload' requires each participant to supply uploadPath; 'mixed' allows per-participant choice.","triggerScenarios":"User sets `\"avatarMode\": \"auto\"`, `\"avatarMode\": \"none\"`, `\"avatarMode\": \"custom\"`, or misspells 'preset'. Also triggered by JSON null or a numeric value.","commonSituations":"Guessing mode names; wanting 'no avatars' and trying 'none' (not supported — there is no off mode); stale config from an older build; case mismatch ('Preset').","solutions":["Set avatarMode to one of: preset, upload, mixed.","For 'no avatars', choose 'preset' and rely on nicknameMode/container styling, or open a feature request — there is no 'off' mode.","Pair the mode with required fields: upload requires uploadPath per participant; preset disallows uploadPath (see errors 578/579).","Pre-validate: `assert c['avatarMode'] in {'preset','upload','mixed'}`."],"exampleFix":"// before\n{\"avatarMode\": \"auto\"}\n# -> ValueError: Unsupported avatarMode: auto\n\n// after\n{\"avatarMode\": \"upload\", \"participants\": {\"Alice\": {\"uploadPath\": \"/abs/avatar.png\"}}}","handlingStrategy":"validation","validationCode":"ALLOWED_AVATAR_MODES = {\"preset\", \"upload\", \"mixed\"}\n\nif config[\"avatarMode\"] not in ALLOWED_AVATAR_MODES:\n    raise SystemExit(\n        f\"avatarMode must be one of {sorted(ALLOWED_AVATAR_MODES)}; \"\n        f\"got {config['avatarMode']!r}\"\n    )","typeGuard":"def is_avatar_mode(value) -> bool:\n    return value in {\"preset\", \"upload\", \"mixed\"}","tryCatchPattern":"try:\n    validate_config(config)\nexcept ValueError as exc:\n    raise SystemExit(f\"Config error: {exc}\") from exc","preventionTips":["Pair avatarMode with the required participant fields at validation time (upload needs uploadPath, preset forbids it).","Document that there is no 'off' mode for avatars.","Use jsonschema with an enum constraint to catch typos at config load.","Prefer 'mixed' when participants vary."],"tags":["config","validation","chat-overlay","avatar"],"backgroundTag":null,"analyzedSha":"5be4028344c2eb4c667c5a97bda8f750c5597ef7","analyzedAt":"2026-08-12T12:03:58.812Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}