{"record":{"id":"039f5e6d4d637eb3","repo":"headroomlabs-ai/headroom","slug":"must-be-field-minimum","errorCode":null,"errorMessage":"must be >= {field.minimum}","messagePattern":"must be >= (.+?)","errorType":"validation","errorClass":"ValueError","httpStatus":422,"severity":"error","filePath":"headroom/settings_store.py","lineNumber":787,"sourceCode":"        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\n        else:\n            try:","sourceCodeStart":769,"sourceCodeEnd":805,"githubUrl":"https://github.com/headroomlabs-ai/headroom/blob/322425c43bffde1ed0b64fecf3cf5951565dd82b/headroom/settings_store.py#L769-L805","documentation":"ValueError from _coerce in headroom/settings_store.py when a numeric field's coerced value is below the field's declared minimum (field.minimum, checked at line 787). Each SettingField in the SETTINGS registry declares its own bounds; the message includes the exact bound. Surfaces via SettingsValidationError.field_errors under the field key.","triggerScenarios":"save({'max_tokens': 0}) when the field declares minimum=1; setting a timeout to 0 or a negative retry count; decrementing a setting in a loop until it crosses the floor.","commonSituations":"Scripts clamping values with the wrong comparison (>= vs >); porting configs from an older version where the minimum was lowered/raised; UI number inputs without min attributes letting users type 0.","solutions":["Raise the value to at least the minimum stated in the error message.","Check the field's declared minimum in headroom.settings_store.SETTINGS to know the allowed range.","If the bound feels wrong for your use, open an issue or use a different field rather than fighting validation."],"exampleFix":"# before\nsave({'max_retries': -1})  # ValueError: must be >= 0\n\n# after\nsave({'max_retries': 0})","handlingStrategy":"validation","validationCode":"from headroom.settings_store import _BY_KEY\n\ndef within_bounds(key: str, v: int | float) -> bool:\n    f = _BY_KEY.get(key)\n    return f is None or f.minimum is None or v >= f.minimum","typeGuard":"def in_range(key: str, v) -> bool:\n    f = _BY_KEY.get(key)\n    if f is None: return True\n    if f.minimum is not None and v < f.minimum: return False\n    if f.maximum is not None and v > f.maximum: return False\n    return True","tryCatchPattern":"except SettingsValidationError as e:\n    for key, msg in e.field_errors.items():\n        if 'must be >=' in msg:\n            floor = _BY_KEY[key].minimum\n            payload[key] = max(payload[key], floor)  # clamp\n    store.save(payload)","preventionTips":["Set min/max attributes on UI inputs from the field metadata.","Clamp user-supplied numbers to [minimum, maximum] before save."],"tags":["settings","validation","range"],"backgroundTag":null,"analyzedSha":"322425c43bffde1ed0b64fecf3cf5951565dd82b","analyzedAt":"2026-08-15T01:03:05.481Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}