headroomlabs-ai/headroom · error · ValueError
expected a number, got {value!r}
Error message
expected a number, got {value!r} What it means
ValueError from _coerce in headroom/settings_store.py when an 'int'/'float' field receives a Python bool. Because bool is a subclass of int in Python, this is rejected explicitly (settings_store.py:776) so that a JSON true/false can never silently become 1/0. The error lands in SettingsValidationError.field_errors under the field's key.
Source
Thrown at headroom/settings_store.py:776
every type except a plain ``bool``, which becomes ``False``). Raises
``ValueError`` on bad input so callers can surface a per-field message.
"""
if value is None:
return None
if field.type in ("bool", "optional-bool"):
if isinstance(value, bool):
return value
token = str(value).strip().lower()
if field.type == "optional-bool" and token == "":
return None
if token in ("1", "true", "yes", "on"):
return True
if token in ("0", "false", "no", "off", ""):
return False
raise ValueError(f"expected a boolean, got {value!r}")
if field.type in ("int", "float"):
if isinstance(value, bool): # bool is an int subclass — reject explicitly
raise ValueError(f"expected a number, got {value!r}")
number: int | float
if field.type == "int":
if isinstance(value, float) and not value.is_integer():
raise ValueError(f"expected an integer, got {value!r}")
number = int(value)
else:
number = float(value)
if not math.isfinite(number):
raise ValueError(f"expected a finite number, got {value!r}")
if field.minimum is not None and number < field.minimum:
raise ValueError(f"must be >= {field.minimum}")
if field.maximum is not None and number > field.maximum:
raise ValueError(f"must be <= {field.maximum}")
return number
if field.type == "enum":
token = str(value)
if token not in field.choices:
raise ValueError(f"{token!r} not one of {list(field.choices)}")View on GitHub (pinned to 322425c43b)
Solutions
- Send an actual number instead of a boolean for that field.
- If the value comes from YAML/JSON parsing, quote it ('5' not 5-bool mixups) or fix the upstream schema mapping.
- Check the field's type in the error message context (the field_errors key tells you which field).
Example fix
# before
save({'max_concurrent': True}) # ValueError: expected a number, got True
# after
save({'max_concurrent': 1}) Defensive patterns
Strategy: validation
Validate before calling
def is_number_like(v) -> bool:
return not isinstance(v, bool) and isinstance(v, (int, float)) Type guard
from typing import Any
def is_numeric(v: Any) -> bool:
return not isinstance(v, bool) and isinstance(v, (int, float)) Try / catch
except SettingsValidationError as e:
for key, msg in e.field_errors.items():
if 'expected a number' in msg and isinstance(payload[key], bool):
payload[key] = int(payload[key])
store.save(payload) Prevention
- Never map boolean UI controls onto numeric settings fields.
- In YAML configs, quote numeric-looking values to avoid implicit type coercion.
When it happens
Trigger: Saving a numeric setting with a JSON boolean, e.g. save({'max_retries': True}) or an env string payload parsed by the caller into True before being handed to the store. Typical when a form toggle is wired to the wrong field name.
Common situations: Frontend forms mapping a checkbox to a numeric config field; YAML/env templating where 'true' gets parsed to a bool by a config loader (yaml.safe_load turns unquoted true/1-adjacent values into bools) before reaching the store.
Related errors
- expected a boolean, got {value!r}
- expected an integer, got {value!r}
- expected a finite number, got {value!r}
- must be >= {field.minimum}
- must be <= {field.maximum}
AI-assisted analysis of headroomlabs-ai/headroom@322425c43b (2026-08-15).
Data as JSON: /api/errors/67c702374617fcb5.
Report an issue: GitHub.