{"record":{"id":"9f0d4a20fbe3ccc9","repo":"unslothai/unsloth","slug":"fraction-must-be-a-number-not-a-boolean","errorCode":null,"errorMessage":"fraction must be a number, not a boolean","messagePattern":"fraction must be a number, not a boolean","errorType":"validation","errorClass":"ValueError","httpStatus":422,"severity":"warning","filePath":"studio/backend/routes/settings.py","lineNumber":586,"sourceCode":"    # that residency will not fully pin a model larger than this. None means\n    # unlimited (macOS) or not applicable (Windows).\n    memlock_limit_bytes: Optional[int] = None\n\n\nclass VramBudgetPayload(BaseModel):\n    # None clears the stored budget so env/default applies again; it cannot also\n    # mean \"leave untouched\" as the model-memory switches do, since there is one\n    # field. Hence required, not defaulted: with a default, {} would mean \"clear it\"\n    # and a client that dropped the field would silently discard the stored budget.\n    fraction: Optional[float] = Field(ge = VRAM_FRACTION_MIN, le = VRAM_FRACTION_MAX)\n\n    @field_validator(\"fraction\", mode = \"before\")\n    @classmethod\n    def _reject_bool(cls, value: object) -> object:\n        # bool subclasses int, so non-strict parsing turns True into 1.0 and stores\n        # the max budget instead of 422; pydantic coerces before the util's guard.\n        if isinstance(value, bool):\n            raise ValueError(\"fraction must be a number, not a boolean\")\n        return value\n\n\nclass VramBudgetResponse(BaseModel):\n    fraction: float\n    # False when inherited from UNSLOTH_VRAM_FRACTION or the default, so the UI\n    # knows whether clearing it would change anything.\n    is_stored: bool\n    default_fraction: float = VRAM_FRACTION_DEFAULT\n    min_fraction: float = VRAM_FRACTION_MIN\n    max_fraction: float = VRAM_FRACTION_MAX\n    # Read when a load sizes itself, so a change cannot reach a running child.\n    reload_required: bool\n\n\nclass HuggingFaceCachePayload(BaseModel):\n    cache_home: Optional[str] = Field(default = None, max_length = 4096)\n","sourceCodeStart":568,"sourceCodeEnd":604,"githubUrl":"https://github.com/unslothai/unsloth/blob/203007d19051dcd2ae33876786d117c99f6b0368/studio/backend/routes/settings.py#L568-L604","documentation":"Pydantic validation failure (HTTP 422) from a mode='before' validator on VramBudgetPayload.fraction. Because bool subclasses int in Python, non-strict pydantic parsing would silently coerce JSON true to 1.0 and store the maximum VRAM budget; the validator runs before coercion and rejects any boolean value explicitly. Valid floats must also satisfy ge=VRAM_FRACTION_MIN and le=VRAM_FRACTION_MAX.","triggerScenarios":"PUT the VRAM budget endpoint with fraction: true or false in the JSON body; also any truthy value a client serializes as a JS boolean (e.g. fraction: isMax) instead of the intended number.","commonSituations":"JS frontend binding a checkbox/toggle state to fraction; YAML/JSON config generated from a template where a number became a boolean; API consumers testing with literal true.","solutions":["Send an explicit float within [VRAM_FRACTION_MIN, VRAM_FRACTION_MAX], e.g. 0.8; send null to clear the stored budget.","Type-check client-side: reject boolean before submitting.","If you generate payloads from schemas, ensure fraction is a number type, never a bool."],"exampleFix":"// before\nawait api.put('/settings/vram-budget', { fraction: true }); // 422\n\n// after\nif (typeof fraction !== 'number') throw new TypeError('fraction must be a number');\nawait api.put('/settings/vram-budget', { fraction: 0.8 });","handlingStrategy":"type-guard","validationCode":"if (typeof fraction !== 'number' && fraction !== null) {\n  throw new TypeError('fraction must be a number or null');\n}\nawait api.put('/settings/vram-budget', { fraction });","typeGuard":"function isVramFraction(v: unknown): v is number {\n  return typeof v === 'number' && Number.isFinite(v) && v >= 0 && v <= 1;\n}","tryCatchPattern":"try { await api.put('/settings/vram-budget', { fraction }); }\ncatch (e) {\n  if (e.status === 422 && /boolean/.test(e.detail?.toString() ?? '')) { fixPayloadType('fraction'); return; }\n  throw e;\n}","preventionTips":["Never bind a boolean control to numeric settings fields.","Send null (not 0, not false) to clear the budget.","Run generated payloads through a JSON schema with type:number."],"tags":["pydantic","validation","http-422","boolean-coercion","vram"],"backgroundTag":null,"analyzedSha":"203007d19051dcd2ae33876786d117c99f6b0368","analyzedAt":"2026-08-15T02:48:39.846Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}