{"record":{"id":"07259cab69503a97","repo":"OpenBMB/ChatDev","slug":"duration-unit-must-be-one-of-join-valid-uni","errorCode":null,"errorMessage":"duration_unit must be one of: {', '.join(valid_units)}","messagePattern":"duration_unit must be one of: (.+?)","errorType":"validation","errorClass":"ConfigError","httpStatus":null,"severity":"error","filePath":"entity/configs/node/loop_timer.py","lineNumber":49,"sourceCode":"        mapping = require_mapping(data or {}, path)\n        max_duration_raw = mapping.get(\"max_duration\", 60.0)\n        try:\n            max_duration = float(max_duration_raw)\n        except (TypeError, ValueError) as exc:  # pragma: no cover - defensive\n            raise ConfigError(\n                \"max_duration must be a number\",\n                extend_path(path, \"max_duration\"),\n            ) from exc\n\n        if max_duration <= 0:\n            raise ConfigError(\n                \"max_duration must be > 0\", extend_path(path, \"max_duration\")\n            )\n\n        duration_unit = str(mapping.get(\"duration_unit\", \"seconds\"))\n        valid_units = [\"seconds\", \"minutes\", \"hours\"]\n        if duration_unit not in valid_units:\n            raise ConfigError(\n                f\"duration_unit must be one of: {', '.join(valid_units)}\",\n                extend_path(path, \"duration_unit\"),\n            )\n\n        reset_on_emit = bool(mapping.get(\"reset_on_emit\", True))\n        message = optional_str(mapping, \"message\", path)\n        passthrough = bool(mapping.get(\"passthrough\", False))\n\n        return cls(\n            max_duration=max_duration,\n            duration_unit=duration_unit,\n            reset_on_emit=reset_on_emit,\n            message=message,\n            passthrough=passthrough,\n            path=path,\n        )\n\n    def validate(self) -> None:","sourceCodeStart":31,"sourceCodeEnd":67,"githubUrl":"https://github.com/OpenBMB/ChatDev/blob/4fb2db0ea90375ce1059f44fe03ffbd191a7a169/entity/configs/node/loop_timer.py#L31-L67","documentation":"LoopTimerConfig.from_dict validates the optional 'duration_unit' (default 'seconds') against exactly ['seconds', 'minutes', 'hours']. Unlike input_mode, this check is case-sensitive with no normalization — str() is applied, so the raw spelling must match.","triggerScenarios":"Passing duration_unit: 'Seconds' (capitalized), 's', 'min', 'm', 'day', or 'days'. Only the exact lowercase strings 'seconds', 'minutes', 'hours' pass.","commonSituations":"Assuming case-insensitivity like other fields in this library; using shorthand units from other config formats; typos.","solutions":["Use exactly 'seconds', 'minutes', or 'hours' (lowercase)","Omit duration_unit to keep the 'seconds' default","Normalize user input: unit.strip().lower() before building the dict, and map shorthand like 's'/'sec' to 'seconds'"],"exampleFix":"# before\n{\"max_duration\": 5, \"duration_unit\": \"Minutes\"}\n# after\n{\"max_duration\": 5, \"duration_unit\": \"minutes\"}","handlingStrategy":"type-guard","validationCode":"UNITS = {'seconds', 'minutes', 'hours'}\nu = str(data.get('duration_unit', 'seconds'))\nif u not in UNITS:\n    data['duration_unit'] = 'seconds'","typeGuard":"def unit_ok(data: dict) -> bool:\n    return str(data.get('duration_unit', 'seconds')) in {'seconds', 'minutes', 'hours'}","tryCatchPattern":"try:\n    LoopTimerConfig.from_dict(data, path='lt')\nexcept ConfigError as e:\n    if 'duration_unit' in e.path:\n        data['duration_unit'] = 'seconds'\n        LoopTimerConfig.from_dict(data, path='lt')\n    else:\n        raise","preventionTips":["Remember this check is case-sensitive, unlike input_mode","Expose only the three allowed values in UIs","Normalize shorthand ('s','min') to full words at the boundary"],"tags":["config","timer","enum","validation","python"],"backgroundTag":"invalid-enum-value","analyzedSha":"4fb2db0ea90375ce1059f44fe03ffbd191a7a169","analyzedAt":"2026-08-27T14:35:29.622Z","schemaVersion":2},"datasetVersion":"2026-08-27T19:17:21.184Z"}