{"record":{"id":"60f6f250806112e6","repo":"headroomlabs-ai/headroom","slug":"expected-a-boolean-got-value-r","errorCode":null,"errorMessage":"expected a boolean, got {value!r}","messagePattern":"expected a boolean, got (.+?)","errorType":"validation","errorClass":"ValueError","httpStatus":422,"severity":"error","filePath":"headroom/settings_store.py","lineNumber":773,"sourceCode":"    \"\"\"Coerce a raw JSON/env value to the field's Python type.\n\n    Returns ``None`` for null and empty values (empty coerces to ``None`` for\n    every type except a plain ``bool``, which becomes ``False``). Raises\n    ``ValueError`` on bad input so callers can surface a per-field message.\n    \"\"\"\n    if value is None:\n        return None\n    if field.type in (\"bool\", \"optional-bool\"):\n        if isinstance(value, bool):\n            return value\n        token = str(value).strip().lower()\n        if field.type == \"optional-bool\" and token == \"\":\n            return None\n        if token in (\"1\", \"true\", \"yes\", \"on\"):\n            return True\n        if token in (\"0\", \"false\", \"no\", \"off\", \"\"):\n            return False\n        raise ValueError(f\"expected a boolean, got {value!r}\")\n    if field.type in (\"int\", \"float\"):\n        if isinstance(value, bool):  # bool is an int subclass — reject explicitly\n            raise ValueError(f\"expected a number, got {value!r}\")\n        number: int | float\n        if field.type == \"int\":\n            if isinstance(value, float) and not value.is_integer():\n                raise ValueError(f\"expected an integer, got {value!r}\")\n            number = int(value)\n        else:\n            number = float(value)\n            if not math.isfinite(number):\n                raise ValueError(f\"expected a finite number, got {value!r}\")\n        if field.minimum is not None and number < field.minimum:\n            raise ValueError(f\"must be >= {field.minimum}\")\n        if field.maximum is not None and number > field.maximum:\n            raise ValueError(f\"must be <= {field.maximum}\")\n        return number\n    if field.type == \"enum\":","sourceCodeStart":755,"sourceCodeEnd":791,"githubUrl":"https://github.com/headroomlabs-ai/headroom/blob/322425c43bffde1ed0b64fecf3cf5951565dd82b/headroom/settings_store.py#L755-L791","documentation":"ValueError from _coerce in headroom/settings_store.py when a field of type 'bool' or 'optional-bool' receives a value that is neither a real bool nor a string token in {1,true,yes,on} / {0,false,no,off,''}. Note the check is case-insensitive and strips whitespace, so ' True ' is fine, but anything else ('maybe', '2', 'enabled') raises. The ValueError is collected into SettingsValidationError.field_errors by the caller.","triggerScenarios":"Saving a bool setting with an unsupported string (HEADROOM-style env value 'ENABLED', 'y', '2') or a non-string non-bool JSON value like 2 or [True]. Example: save({'optimize': 'sometimes'}) where 'optimize' is a bool field.","commonSituations":"Operators copying boolean conventions from other tools ('y'/'enabled'/'on-request'); JSON payloads from UIs that send 1/2 instead of true/false; shell scripts passing $FLAG that is empty-but-quoted oddly combined with a plain bool type (empty string maps to False for 'bool' but None for 'optional-bool').","solutions":["Change the value to one of the accepted tokens: true/false, 1/0, yes/no, on/off (case-insensitive), or a real JSON boolean.","If the value legitimately may be absent, make sure the field is optional-bool and pass null/'' rather than a placeholder word.","If you must accept other spellings, normalize them to true/false in your own code before calling the settings API."],"exampleFix":"# before\nsave({'proxy_optimize': 'ENABLED'})  # ValueError: expected a boolean, got 'ENABLED'\n\n# after\nsave({'proxy_optimize': 'on'})  # or True / 'true'","handlingStrategy":"validation","validationCode":"TRUE = {'1', 'true', 'yes', 'on'}\nFALSE = {'0', 'false', 'no', 'off', ''}\n\ndef valid_bool_token(v) -> bool:\n    return isinstance(v, bool) or str(v).strip().lower() in TRUE | FALSE","typeGuard":"def is_bool_like(v) -> bool:\n    return isinstance(v, bool) or str(v).strip().lower() in {'1','true','yes','on','0','false','no','off',''}","tryCatchPattern":"from headroom.settings_store import SettingsValidationError\ntry:\n    store.save(payload)\nexcept SettingsValidationError as e:\n    for key, msg in e.field_errors.items():\n        if 'expected a boolean' in msg:\n            payload[key] = bool(payload[key])  # or fix upstream\n    store.save(payload)","preventionTips":["Use plain true/false in JSON payloads.","Document the accepted env tokens next to every boolean you expose to operators."],"tags":["settings","validation","boolean"],"backgroundTag":null,"analyzedSha":"322425c43bffde1ed0b64fecf3cf5951565dd82b","analyzedAt":"2026-08-15T01:03:05.481Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}