{"record":{"id":"6267472a065b5dd1","repo":"OpenBMB/ChatDev","slug":"max-duration-must-be-a-number","errorCode":null,"errorMessage":"max_duration must be a number","messagePattern":"max_duration must be a number","errorType":"validation","errorClass":"ConfigError","httpStatus":null,"severity":"error","filePath":"entity/configs/node/loop_timer.py","lineNumber":36,"sourceCode":"class LoopTimerConfig(BaseConfig):\n    \"\"\"Configuration schema for the loop timer node type.\"\"\"\n\n    max_duration: float = 60.0\n    duration_unit: str = \"seconds\"\n    reset_on_emit: bool = True\n    message: Optional[str] = None\n    passthrough: bool = False\n\n    @classmethod\n    def from_dict(\n        cls, data: Mapping[str, Any] | None, *, path: str\n    ) -> \"LoopTimerConfig\":\n        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))","sourceCodeStart":18,"sourceCodeEnd":54,"githubUrl":"https://github.com/OpenBMB/ChatDev/blob/4fb2db0ea90375ce1059f44fe03ffbd191a7a169/entity/configs/node/loop_timer.py#L18-L54","documentation":"LoopTimerConfig.from_dict could not coerce 'max_duration' (default 60.0) to float. Non-numeric strings, None, or sequences raise TypeError/ValueError and surface as this config error.","triggerScenarios":"Passing max_duration as 'one minute', null, or a list. Numeric strings like '30' parse fine via float(), so only genuinely non-numeric values fail.","commonSituations":"Blank YAML values becoming None; unit-aware strings ('30s') pasted where a bare number is expected; env-var defaults that are empty strings.","solutions":["Set max_duration to a number (float/int) in the config's base unit","Move units into duration_unit ('seconds'/'minutes'/'hours'), not into the number","Sanitize env-derived values: float(value or 60.0)"],"exampleFix":"# before\n{\"max_duration\": \"30s\"}\n# after\n{\"max_duration\": 30, \"duration_unit\": \"seconds\"}","handlingStrategy":"validation","validationCode":"raw = data.get('max_duration', 60.0)\ntry:\n    data['max_duration'] = float(raw)\nexcept (TypeError, ValueError):\n    data['max_duration'] = 60.0","typeGuard":"def duration_coercible(data: dict) -> bool:\n    try:\n        float(data.get('max_duration', 60.0))\n        return True\n    except (TypeError, ValueError):\n        return False","tryCatchPattern":"try:\n    LoopTimerConfig.from_dict(data, path='lt')\nexcept ConfigError as e:\n    if 'must be a number' in str(e):\n        data['max_duration'] = 60.0\n        LoopTimerConfig.from_dict(data, path='lt')\n    else:\n        raise","preventionTips":["Keep units in duration_unit, numbers bare","float() env values at load with a default","Never paste '30s'-style values"],"tags":["config","timer","validation","python"],"backgroundTag":"type-coercion-failed","analyzedSha":"4fb2db0ea90375ce1059f44fe03ffbd191a7a169","analyzedAt":"2026-08-27T14:35:29.622Z","schemaVersion":2},"datasetVersion":"2026-08-27T19:17:21.184Z"}