headroomlabs-ai/headroom · error · ValueError
expected an integer, got {value!r}
Error message
expected an integer, got {value!r} What it means
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.
Source
Thrown at headroom/settings_store.py:780
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)}")
return token
if field.type == "csv-list":
tokens = value if isinstance(value, list | tuple) else str(value).split(",")
tokens = [str(token).strip() for token in tokens]View on GitHub (pinned to 322425c43b)
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.
Example fix
# before
save({'max_tokens': 4096 * 0.75}) # 3072.0 fine; 4095.5 raises
# after
save({'max_tokens': int(4096 * 0.75)}) Defensive patterns
Strategy: validation
Validate before calling
def valid_int(v) -> bool:
return not isinstance(v, bool) and (
isinstance(v, int) or (isinstance(v, float) and v.is_integer())
) Type guard
def is_int_like(v) -> bool:
return not isinstance(v, bool) and (isinstance(v, int) or (isinstance(v, float) and float(v).is_integer())) Try / catch
except SettingsValidationError as e:
for key, msg in e.field_errors.items():
if 'expected an integer' in msg:
payload[key] = round(payload[key])
store.save(payload) Prevention
- Coerce JS-side numbers with Math.round before submitting settings.
- Prefer int-typed inputs in forms (step=1) to block fractional entry.
When it happens
Trigger: 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.
Common situations: JavaScript clients (all numbers are floats) sending fractional values; spreadsheet- or formula-derived configs; dividing values then forgetting to round.
Related errors
- expected a finite number, got {value!r}
- expected a boolean, got {value!r}
- expected a 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/b9609a63dbe3dbc2.
Report an issue: GitHub.