{"record":{"id":"b68cf6349d65c865","repo":"nexu-io/open-design","slug":"unsupported-container-config-container","errorCode":null,"errorMessage":"Unsupported container: {config['container']}","messagePattern":"Unsupported container: (.+?)","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"skills/chat-motion-overlay/scripts/build_chat_overlay_spec.py","lineNumber":123,"sourceCode":"\ndef is_flag(value: str) -> bool:\n    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}\")","sourceCodeStart":105,"sourceCodeEnd":141,"githubUrl":"https://github.com/nexu-io/open-design/blob/5be4028344c2eb4c667c5a97bda8f750c5597ef7/skills/chat-motion-overlay/scripts/build_chat_overlay_spec.py#L105-L141","documentation":"Raised by validate_config in build_chat_overlay_spec.py when config['container'] is not in ALLOWED_CONTAINERS = {\"none\",\"wechat\",\"telegram\",\"messenger\"}. The config is built by deep-copying DEFAULT_CONFIG (container='wechat') then overlaying the user JSON, so this fires only when the user's config sets container to an unrecognized string.","triggerScenarios":"User supplies a config JSON with `\"container\": \"whatsapp\"` or `\"container\": \"line\"`, a typo like `\"WeChat\"` (case-sensitive), or `\"container\": \"slack\"`. Also fires on null/number values via JSON.","commonSituations":"Guessing container names from common chat apps not in the allowed set; case mismatch (WeChat vs wechat); legacy config from a previous version where the allowed set differed; copying an example from an unrelated tool.","solutions":["Set container to one of: none, wechat, telegram, messenger.","Use lowercase exactly as listed (the check is case-sensitive).","If you need no app chrome, use 'none' (but note the deviceFrame constraint in error 576).","Validate the config JSON before running: `python3 -c \"import json; c=json.load(open('cfg.json')); assert c['container'] in {'none','wechat','telegram','messenger'}\"`."],"exampleFix":"// before (cfg.json)\n{\"container\": \"whatsapp\"}\n# -> ValueError: Unsupported container: whatsapp\n\n// after\n{\"container\": \"telegram\"}","handlingStrategy":"validation","validationCode":"ALLOWED_CONTAINERS = {\"none\", \"wechat\", \"telegram\", \"messenger\"}\n\nif config[\"container\"] not in ALLOWED_CONTAINERS:\n    raise SystemExit(\n        f\"container must be one of {sorted(ALLOWED_CONTAINERS)}; got {config['container']!r}\"\n    )","typeGuard":"def is_container(value) -> bool:\n    return value in {\"none\", \"wechat\", \"telegram\", \"messenger\"}","tryCatchPattern":"try:\n    validate_config(config)\nexcept ValueError as exc:\n    raise SystemExit(f\"Config error: {exc}\") from exc","preventionTips":["Validate config with jsonschema before running the build.","Keep allowed-value sets in one place and generate docs/help from them.","Treat config values as case-sensitive lowercase enums.","Fail fast at config load, not deep in the render pipeline."],"tags":["config","validation","chat-overlay","json"],"backgroundTag":null,"analyzedSha":"5be4028344c2eb4c667c5a97bda8f750c5597ef7","analyzedAt":"2026-08-12T12:03:58.812Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}