{"record":{"id":"3b0873e2cbcb99d2","repo":"calesthio/OpenMontage","slug":"face-choose-must-be-a-list-of-face-choice-objects","errorCode":null,"errorMessage":"face_choose must be a list of face choice objects","messagePattern":"face_choose must be a list of face choice objects","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"tools/avatar/kling_lip_sync.py","lineNumber":397,"sourceCode":"        }\n        self._copy_common_task_fields(inputs, payload)\n        return {\n            \"protocol\": \"classic\",\n            \"path\": \"/v1/videos/advanced-lip-sync\",\n            \"payload\": payload,\n            \"operation\": \"advanced_lip_sync\",\n            \"model\": \"kling-official-lip-sync\",\n            \"audio_source\": audio_source,\n        }\n\n    @staticmethod\n    def _normalize_face_choose(inputs: dict[str, Any]) -> list[dict[str, Any]]:\n        if inputs.get(\"face_choose\"):\n            raw = inputs[\"face_choose\"]\n            if isinstance(raw, dict):\n                raw = [raw]\n            if not isinstance(raw, list):\n                raise ValueError(\"face_choose must be a list of face choice objects\")\n            normalized: list[dict[str, Any]] = []\n            for item in raw:\n                if isinstance(item, str):\n                    normalized.append({\"face_id\": item})\n                elif isinstance(item, dict):\n                    if not (item.get(\"face_id\") or item.get(\"id\")):\n                        raise ValueError(\"face_choose items must include face_id\")\n                    record = dict(item)\n                    if \"face_id\" not in record and record.get(\"id\"):\n                        record[\"face_id\"] = record.pop(\"id\")\n                    normalized.append(record)\n                else:\n                    raise ValueError(\"face_choose items must be strings or objects\")\n            return normalized\n        if inputs.get(\"face_id\"):\n            return [{\"face_id\": str(inputs[\"face_id\"])}]\n        return []\n","sourceCodeStart":379,"sourceCodeEnd":415,"githubUrl":"https://github.com/calesthio/OpenMontage/blob/95e1c3d0ab93482159818560f6a8c8e866b9139f/tools/avatar/kling_lip_sync.py#L379-L415","documentation":"ValueError from _normalize_face_choose when the face_choose input is neither a dict (auto-wrapped into a list) nor a list — e.g. a string like '1,2', an int, or None-like truthy garbage. The normalizer is lenient about shape (dict, list, string items) but requires a JSON-array-compatible value at the top.","triggerScenarios":"Passing face_choose='1' (bare string — use face_id instead), face_choose=1, or a comma-joined string of ids.","commonSituations":"Config/LLM front-ends serializing the selection as a delimited string; passing face_id value under the face_choose key.","solutions":["Pass a list: face_choose=[{'face_id': '1'}] or ['1'].","A single dict is fine: face_choose={'face_id': '1'} — it is wrapped automatically.","For a bare id string, use the face_id input, not face_choose."],"exampleFix":"// before\nresult = tool.run({..., \"face_choose\": \"1\"})\n\n// after\nresult = tool.run({..., \"face_choose\": [\"1\"]})  # or simply \"face_id\": \"1\"","handlingStrategy":"type-guard","validationCode":"raw = inputs.get(\"face_choose\")\nif isinstance(raw, str):\n    raw = [raw]  # or reject and use face_id instead\nif isinstance(raw, dict):\n    raw = [raw]\nassert isinstance(raw, list), \"face_choose must be a list\"","typeGuard":"def is_valid_face_choose(value: object) -> bool:\n    return isinstance(value, (list, dict))","tryCatchPattern":null,"preventionTips":["Pass face_choose as a list (or single dict); pass bare id strings via face_id","Validate shapes at the config/LLM boundary before they reach the tool"],"tags":["kling","lip-sync","type-validation","face-selection"],"backgroundTag":null,"analyzedSha":"95e1c3d0ab93482159818560f6a8c8e866b9139f","analyzedAt":"2026-08-15T06:31:20.014Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}