unslothai/unsloth · warning · ValueError
Expected numbers, got a boolean.
Error message
Expected numbers, got a boolean.
What it means
Pydantic validation failure (HTTP 422) from the same _no_booleans before-validator, list branch: the field value is a list (in practice gpu_ids) and at least one element is a boolean. Without the guard, [true, 1] would coerce to [1, 1] and silently duplicate GPU 1 instead of 400/422.
Source
Thrown at studio/backend/routes/settings.py:736
"custom_context_length",
"spec_draft_n_max",
"n_parallel",
"n_batch",
"n_ubatch",
"gpu_layers",
"n_cpu_moe",
"gpu_ids",
mode = "before",
)
@classmethod
def _no_booleans(cls, value: Any) -> Any:
# bool subclasses int and pydantic parses non-strictly, so `true` arrives as 1: a
# payload could pin GPU 1 or set a one-token context. _bounded_int rejects bools but
# never sees one, since coercion happens here first. Only bools, so lax parsing stays.
if isinstance(value, bool):
raise ValueError("Expected a number, got a boolean.")
if isinstance(value, list) and any(isinstance(item, bool) for item in value):
raise ValueError("Expected numbers, got a boolean.")
return value
class ModelOverridesResponse(BaseModel):
overrides: dict[str, dict]
# Filled only when the caller named a model: the entry ITS load would apply,
# resolved here rather than in the browser. The folding rules are Python's
# (casefold is not toLowerCase, and an ambiguous fold matches nothing on
# purpose), so a client mirroring them can only approximate.
resolved: Optional[dict] = None
resolved_key: Optional[str] = None
def _upload_limit_response(limit_mb: int) -> UploadLimitResponse:
return UploadLimitResponse(
max_upload_size_mb = limit_mb,
max_upload_size_bytes = upload_limit_bytes(limit_mb),
max_upload_size_label = upload_limit_label(limit_mb),View on GitHub (pinned to 203007d190)
Solutions
- Send only integers in gpu_ids, e.g. [0, 1].
- Map checkbox state to indices client-side: gpus.map((checked, i) => checked ? i : null).filter(v => v !== null).
- Assert every element Number.isInteger before submit.
Example fix
// before
await api.put(url, { gpu_ids: [true, false] }); // 422
// after
const gpuIds = gpus.map((checked, i) => (checked ? i : -1)).filter(i => i >= 0);
if (!gpuIds.every(Number.isInteger)) throw new TypeError('gpu_ids must be integers');
await api.put(url, { gpu_ids: gpuIds }); Defensive patterns
Strategy: type-guard
Validate before calling
if (Array.isArray(payload.gpu_ids) && payload.gpu_ids.some(Number.isBoolean)) {
throw new TypeError('gpu_ids must contain only integers');
} Type guard
function isGpuIdList(v: unknown): v is number[] {
return Array.isArray(v) && v.every(n => Number.isInteger(n) && n >= 0);
} Try / catch
try { await api.put(overridesUrl, payload); }
catch (e) {
if (e.status === 422 && /Expected numbers/.test(e.detail?.toString() ?? '')) { payload.gpu_ids = payload.gpu_ids.filter(Number.isInteger); return api.put(overridesUrl, payload); }
throw e;
} Prevention
- Map checkbox state to GPU indices, not the booleans themselves.
- Filter arrays through Number.isInteger before submit.
- Add unit tests asserting gpu_ids contains no booleans.
When it happens
Trigger: PUT model overrides with gpu_ids: [true] or [0, false, 2]; UI checkboxes feeding directly into the GPU id array.
Common situations: Checkbox-driven GPU selector where checked=true is pushed into the ids array; mixed data from form state; scripted payloads reusing toggle state.
Related errors
- fraction must be a number, not a boolean
- Expected a number, got a boolean.
- Provide either content_base64 or file_ids, not both
- Provide either content_base64 or file_ids
- file_ids must not be empty
AI-assisted analysis of unslothai/unsloth@203007d190 (2026-08-15).
Data as JSON: /api/errors/1cc173f94d8af87d.
Report an issue: GitHub.