{"record":{"id":"d4d4f8a152dc4f68","repo":"headroomlabs-ai/headroom","slug":"expected-a-finite-number-got-value-r","errorCode":null,"errorMessage":"expected a finite number, got {value!r}","messagePattern":"expected a finite number, got (.+?)","errorType":"validation","errorClass":"ValueError","httpStatus":422,"severity":"error","filePath":"headroom/settings_store.py","lineNumber":785,"sourceCode":"        if field.type == \"optional-bool\" and token == \"\":\n            return None\n        if token in (\"1\", \"true\", \"yes\", \"on\"):\n            return True\n        if token in (\"0\", \"false\", \"no\", \"off\", \"\"):\n            return False\n        raise ValueError(f\"expected a boolean, got {value!r}\")\n    if field.type in (\"int\", \"float\"):\n        if isinstance(value, bool):  # bool is an int subclass — reject explicitly\n            raise ValueError(f\"expected a number, got {value!r}\")\n        number: int | float\n        if field.type == \"int\":\n            if isinstance(value, float) and not value.is_integer():\n                raise ValueError(f\"expected an integer, got {value!r}\")\n            number = int(value)\n        else:\n            number = float(value)\n            if not math.isfinite(number):\n                raise ValueError(f\"expected a finite number, got {value!r}\")\n        if field.minimum is not None and number < field.minimum:\n            raise ValueError(f\"must be >= {field.minimum}\")\n        if field.maximum is not None and number > field.maximum:\n            raise ValueError(f\"must be <= {field.maximum}\")\n        return number\n    if field.type == \"enum\":\n        token = str(value)\n        if token not in field.choices:\n            raise ValueError(f\"{token!r} not one of {list(field.choices)}\")\n        return token\n    if field.type == \"csv-list\":\n        tokens = value if isinstance(value, list | tuple) else str(value).split(\",\")\n        tokens = [str(token).strip() for token in tokens]\n        tokens = [token for token in tokens if token]\n        return \",\".join(tokens) if tokens else None\n    if field.type == \"header-map\":\n        if isinstance(value, dict):\n            parsed = value","sourceCodeStart":767,"sourceCodeEnd":803,"githubUrl":"https://github.com/headroomlabs-ai/headroom/blob/322425c43bffde1ed0b64fecf3cf5951565dd82b/headroom/settings_store.py#L767-L803","documentation":"ValueError from _coerce in headroom/settings_store.py when a 'float' field receives a value that parses to a non-finite float — NaN, +inf, or -inf (math.isfinite check at line 785). Note the value arrives as a Python float here, so json.loads('Infinity') (which Python's json accepts by default) or float('nan') from a caller's parsing will trigger it. Surfaces via SettingsValidationError.field_errors.","triggerScenarios":"save({'request_timeout': float('inf')}); a JSON payload containing Infinity/NaN literals (Python's json module accepts them); computed ratios like x/0.0 producing inf and then passed to the store.","commonSituations":"Division-by-zero bugs upstream that leak inf into config; numpy calculations returning np.inf/np.nan converted with float(); JSON produced by tools that emit non-standard Infinity literals.","solutions":["Fix the upstream computation so it never yields inf/NaN (guard divisions, use a large finite default).","If the setting means 'no limit', use the field's documented maximum or omit it (null), not infinity.","Validate with math.isfinite() before saving."],"exampleFix":"# before\nsave({'timeout': float(x) / count})  # inf when count == 0\n\n# after\nsave({'timeout': float(x) / count if count else 3600.0})","handlingStrategy":"validation","validationCode":"import math\n\ndef finite_float(v) -> bool:\n    try:\n        return math.isfinite(float(v))\n    except (TypeError, ValueError):\n        return False","typeGuard":"import math\n\ndef is_finite_number(v) -> bool:\n    return not isinstance(v, bool) and isinstance(v, (int, float)) and math.isfinite(v)","tryCatchPattern":"except SettingsValidationError as e:\n    for key, msg in e.field_errors.items():\n        if 'finite' in msg:\n            payload.pop(key, None)  # drop and fall back to default\n    store.save(payload)","preventionTips":["Reject non-standard JSON with parse_constant to catch Infinity/NaN at the boundary.","Use math.isfinite as a final guard on any computed float destined for config."],"tags":["settings","validation","numbers","edge-case"],"backgroundTag":null,"analyzedSha":"322425c43bffde1ed0b64fecf3cf5951565dd82b","analyzedAt":"2026-08-15T01:03:05.481Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}