{"record":{"id":"b9609a63dbe3dbc2","repo":"headroomlabs-ai/headroom","slug":"expected-an-integer-got-value-r","errorCode":null,"errorMessage":"expected an integer, got {value!r}","messagePattern":"expected an integer, got (.+?)","errorType":"validation","errorClass":"ValueError","httpStatus":422,"severity":"error","filePath":"headroom/settings_store.py","lineNumber":780,"sourceCode":"        return None\n    if field.type in (\"bool\", \"optional-bool\"):\n        if isinstance(value, bool):\n            return value\n        token = str(value).strip().lower()\n        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]","sourceCodeStart":762,"sourceCodeEnd":798,"githubUrl":"https://github.com/headroomlabs-ai/headroom/blob/322425c43bffde1ed0b64fecf3cf5951565dd82b/headroom/settings_store.py#L762-L798","documentation":"ValueError from _coerce in headroom/settings_store.py when an 'int' field receives a float that has a fractional part (value.is_integer() is False), e.g. 1.5. Whole floats like 2.0 are accepted and truncated via int(). The error is surfaced per-field through SettingsValidationError.field_errors.","triggerScenarios":"save({'port': 8080.5}), or a computed value like count * 0.5 assigned to an integer field; JSON payloads from JS where every number is a float and one carries a fraction.","commonSituations":"JavaScript clients (all numbers are floats) sending fractional values; spreadsheet- or formula-derived configs; dividing values then forgetting to round.","solutions":["Round or floor the value to a whole number before saving (int(value) if it should truncate).","Fix the upstream computation so it yields an integer (use // or round()).","If a fractional value is legitimate, the field must be a 'float' type in the registry — check the field's declared type."],"exampleFix":"# before\nsave({'max_tokens': 4096 * 0.75})  # 3072.0 fine; 4095.5 raises\n\n# after\nsave({'max_tokens': int(4096 * 0.75)})","handlingStrategy":"validation","validationCode":"def valid_int(v) -> bool:\n    return not isinstance(v, bool) and (\n        isinstance(v, int) or (isinstance(v, float) and v.is_integer())\n    )","typeGuard":"def is_int_like(v) -> bool:\n    return not isinstance(v, bool) and (isinstance(v, int) or (isinstance(v, float) and float(v).is_integer()))","tryCatchPattern":"except SettingsValidationError as e:\n    for key, msg in e.field_errors.items():\n        if 'expected an integer' in msg:\n            payload[key] = round(payload[key])\n    store.save(payload)","preventionTips":["Coerce JS-side numbers with Math.round before submitting settings.","Prefer int-typed inputs in forms (step=1) to block fractional entry."],"tags":["settings","validation","numbers"],"backgroundTag":null,"analyzedSha":"322425c43bffde1ed0b64fecf3cf5951565dd82b","analyzedAt":"2026-08-15T01:03:05.481Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}