{"record":{"id":"d49362e0be846212","repo":"unslothai/unsloth","slug":"expected-a-number-got-a-boolean-d49362","errorCode":null,"errorMessage":"Expected a number, got a boolean.","messagePattern":"Expected a number, got a boolean\\.","errorType":"validation","errorClass":"ValueError","httpStatus":422,"severity":"warning","filePath":"studio/backend/routes/settings.py","lineNumber":734,"sourceCode":"    @field_validator(\n        \"max_seq_length\",\n        \"custom_context_length\",\n        \"spec_draft_n_max\",\n        \"n_parallel\",\n        \"n_batch\",\n        \"n_ubatch\",\n        \"gpu_layers\",\n        \"n_cpu_moe\",\n        \"gpu_ids\",\n        mode = \"before\",\n    )\n    @classmethod\n    def _no_booleans(cls, value: Any) -> Any:\n        # bool subclasses int and pydantic parses non-strictly, so `true` arrives as 1: a\n        # payload could pin GPU 1 or set a one-token context. _bounded_int rejects bools but\n        # never sees one, since coercion happens here first. Only bools, so lax parsing stays.\n        if isinstance(value, bool):\n            raise ValueError(\"Expected a number, got a boolean.\")\n        if isinstance(value, list) and any(isinstance(item, bool) for item in value):\n            raise ValueError(\"Expected numbers, got a boolean.\")\n        return value\n\n\nclass ModelOverridesResponse(BaseModel):\n    overrides: dict[str, dict]\n    # Filled only when the caller named a model: the entry ITS load would apply,\n    # resolved here rather than in the browser. The folding rules are Python's\n    # (casefold is not toLowerCase, and an ambiguous fold matches nothing on\n    # purpose), so a client mirroring them can only approximate.\n    resolved: Optional[dict] = None\n    resolved_key: Optional[str] = None\n\n\ndef _upload_limit_response(limit_mb: int) -> UploadLimitResponse:\n    return UploadLimitResponse(\n        max_upload_size_mb = limit_mb,","sourceCodeStart":716,"sourceCodeEnd":752,"githubUrl":"https://github.com/unslothai/unsloth/blob/203007d19051dcd2ae33876786d117c99f6b0368/studio/backend/routes/settings.py#L716-L752","documentation":"Pydantic validation failure (HTTP 422) from a mode='before' validator applied to the numeric override fields (max_seq_length, custom_context_length, spec_draft_n_max, n_parallel, n_batch, n_ubatch, gpu_layers, n_cpu_moe). Because bool subclasses int, lax pydantic parsing would turn JSON true into 1 — pinning GPU 1 or setting a one-token context. The validator rejects any scalar bool before coercion; downstream _bounded_int never sees one.","triggerScenarios":"PUT model overrides with gpu_layers: true, n_parallel: false, or any of the listed fields set to a JSON boolean instead of an integer.","commonSituations":"JS UI binding a toggle to a numeric field; config generated from YAML where 'on'/'off' became true/false; scripts building payloads dynamically with mixed types.","solutions":["Send integers for all numeric fields (e.g. gpu_layers: 40, n_parallel: 2); use null/omit to leave a field unchanged.","Add a client-side typeof check rejecting booleans for these keys.","Validate generated payloads against a JSON schema with type:'integer' before submitting."],"exampleFix":"// before\nawait api.put(url, { gpu_layers: true }); // 422\n\n// after\nconst overrides = { gpu_layers: Number(gpuLayersInput.value) };\nfor (const [k, v] of Object.entries(overrides)) {\n  if (typeof v === 'boolean') throw new TypeError(`${k} must be a number`);\n}\nawait api.put(url, overrides);","handlingStrategy":"type-guard","validationCode":"const NUMERIC_KEYS = ['max_seq_length','custom_context_length','spec_draft_n_max','n_parallel','n_batch','n_ubatch','gpu_layers','n_cpu_moe'];\nfor (const k of NUMERIC_KEYS) {\n  if (typeof payload[k] === 'boolean') throw new TypeError(`${k} must be a number`);\n}","typeGuard":"function isNumericOverride(v: unknown): v is number {\n  return typeof v === 'number' && Number.isInteger(v);\n}","tryCatchPattern":"try { await api.put(overridesUrl, payload); }\ncatch (e) {\n  if (e.status === 422 && /boolean/.test(e.detail?.toString() ?? '')) { sanitizeNumericFields(); return; }\n  throw e;\n}","preventionTips":["Use number inputs (<input type=number>) for these fields, never toggles.","Validate payload shape against the OpenAPI schema before sending.","Remember null/omitted means 'leave unchanged'; true is never a valid value."],"tags":["pydantic","validation","http-422","boolean-coercion","llm-config"],"backgroundTag":null,"analyzedSha":"203007d19051dcd2ae33876786d117c99f6b0368","analyzedAt":"2026-08-15T02:48:39.846Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}