{"record":{"id":"47136ada40141b5c","repo":"Comfy-Org/ComfyUI","slug":"unexpected-type-for-duration-key-must-be-str-int","errorCode":null,"errorMessage":"Unexpected type for duration key, must be str, int or float","messagePattern":"Unexpected type for duration key, must be str, int or float","errorType":"validation","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"comfy/text_encoders/ace15.py","lineNumber":177,"sourceCode":"        if len(user_metas):\n            meta_yaml = yaml.dump(user_metas, allow_unicode=True, sort_keys=True).strip()\n        else:\n            meta_yaml = \"\"\n        return f\"<think>\\n{meta_yaml}\\n</think>\" if not return_yaml else meta_yaml\n\n    def _metas_to_cap(self, **kwargs) -> str:\n        use_keys = (\"bpm\", \"timesignature\", \"keyscale\", \"duration\")\n        user_metas = { k: kwargs.pop(k, \"N/A\") for k in use_keys }\n        timesignature = user_metas.get(\"timesignature\")\n        if isinstance(timesignature, str) and timesignature.endswith(\"/4\"):\n            user_metas[\"timesignature\"] = timesignature[:-2]\n        duration = user_metas[\"duration\"]\n        if duration == \"N/A\":\n            user_metas[\"duration\"] = \"30 seconds\"\n        elif isinstance(duration, (str, int, float)):\n            user_metas[\"duration\"] = f\"{math.ceil(float(duration))} seconds\"\n        else:\n            raise TypeError(\"Unexpected type for duration key, must be str, int or float\")\n        return \"\\n\".join(f\"- {k}: {user_metas[k]}\" for k in use_keys)\n\n    def tokenize_with_weights(self, text, return_word_ids=False, **kwargs):\n        text = text.strip()\n        text_negative = kwargs.get(\"caption_negative\", text).strip()\n        lyrics = kwargs.get(\"lyrics\", \"\")\n        lyrics_negative = kwargs.get(\"lyrics_negative\", lyrics)\n        duration = kwargs.get(\"duration\", 120)\n        if isinstance(duration, str):\n            duration = float(duration.split(None, 1)[0])\n        language = kwargs.get(\"language\")\n        seed = kwargs.get(\"seed\", 0)\n\n        generate_audio_codes = kwargs.get(\"generate_audio_codes\", True)\n        cfg_scale = kwargs.get(\"cfg_scale\", 2.0)\n        temperature = kwargs.get(\"temperature\", 0.85)\n        top_p = kwargs.get(\"top_p\", 0.9)\n        top_k = kwargs.get(\"top_k\", 0.0)","sourceCodeStart":159,"sourceCodeEnd":195,"githubUrl":"https://github.com/Comfy-Org/ComfyUI/blob/1c6d8d45b3693bfbb32385b410d813a7fd6be216/comfy/text_encoders/ace15.py#L159-L195","documentation":"In comfy/text_encoders/ace15.py, _metas_to_cap builds a metadata caption for the ACE 1.5 music model. The `duration` kwarg is normalized to 'N/A' if absent, formatted when str/int/float, but any other Python type (None, list, dict, bool is technically int but e.g. None) hits the else branch and raises TypeError.","triggerScenarios":"Calling tokenize_with_weights(..., duration=None) or passing duration as a list/dict/tuple from a node or API payload; JSON workflow params deserialized into a non-numeric duration; downstream code that forwards raw widget values without type coercion.","commonSituations":"API clients sending \"duration\": null in JSON (becomes None in Python); custom nodes passing unvalidated widget structs; workflows where the duration widget is optional and yields None instead of the default.","solutions":["Pass duration as a number or numeric string (e.g. 120 or '90'); let the tokenizer default (120) apply by omitting it entirely.","Normalize before the call: duration = float(duration) if duration is not None else 120.","In API payloads, drop the duration key rather than sending null."],"exampleFix":"# before\ntokens = tokenizer.tokenize_with_weights(prompt, duration=None)  # TypeError\n\n# after\nduration = 120 if duration is None else duration\ntokens = tokenizer.tokenize_with_weights(prompt, duration=duration)","handlingStrategy":"type-guard","validationCode":"if duration is not None and not isinstance(duration, (str, int, float)):\n    raise TypeError('duration must be str, int, or float')\nduration = 120 if duration is None else duration","typeGuard":"def is_valid_duration(d) -> bool:\n    return d is None or isinstance(d, (str, int, float)) and not isinstance(d, bool)","tryCatchPattern":"try:\n    tokens = tokenizer.tokenize_with_weights(prompt, duration=duration)\nexcept TypeError as e:\n    if 'duration' in str(e):\n        tokens = tokenizer.tokenize_with_weights(prompt, duration=120)\n    else:\n        raise","preventionTips":["Coerce API-provided duration values with float() before forwarding, defaulting when null.","Treat JSON null as 'use default', never pass it through."],"tags":["text-encoder","ace15","type-validation","audio-metadata"],"backgroundTag":null,"analyzedSha":"1c6d8d45b3693bfbb32385b410d813a7fd6be216","analyzedAt":"2026-08-14T19:37:18.893Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}