{"record":{"id":"86fa42eaadee99b4","repo":"headroomlabs-ai/headroom","slug":"token-r-not-one-of-list-field-choices","errorCode":null,"errorMessage":"{token!r} not one of {list(field.choices)}","messagePattern":"(.+?) not one of (.+?)","errorType":"validation","errorClass":"ValueError","httpStatus":422,"severity":"error","filePath":"headroom/settings_store.py","lineNumber":794,"sourceCode":"            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\":\n        token = str(value)\n        if token not in field.choices:\n            raise ValueError(f\"{token!r} not one of {list(field.choices)}\")\n        return token\n    if field.type == \"csv-list\":\n        tokens = value if isinstance(value, list | tuple) else str(value).split(\",\")\n        tokens = [str(token).strip() for token in tokens]\n        tokens = [token for token in tokens if token]\n        return \",\".join(tokens) if tokens else None\n    if field.type == \"header-map\":\n        if isinstance(value, dict):\n            parsed = value\n        else:\n            try:\n                parsed = json.loads(str(value))\n            except (ValueError, TypeError) as exc:\n                raise ValueError(\"expected a JSON object of header name/value strings\") from exc\n        if not isinstance(parsed, dict) or not all(\n            isinstance(k, str) and isinstance(v, str) for k, v in parsed.items()\n        ):\n            raise ValueError(\"expected a JSON object of header name/value strings\")","sourceCodeStart":776,"sourceCodeEnd":812,"githubUrl":"https://github.com/headroomlabs-ai/headroom/blob/322425c43bffde1ed0b64fecf3cf5951565dd82b/headroom/settings_store.py#L776-L812","documentation":"ValueError from _coerce in headroom/settings_store.py when an 'enum' field receives a value whose string form is not in the field's declared choices (line 794). The message lists the accepted choices. Note the value is coerced with str(), so numbers are compared by their string form. Reported per-field via SettingsValidationError.field_errors.","triggerScenarios":"save({'proxy_mode': 'Cache'}) when choices are lowercase; passing a mode value from an older headroom version whose name was renamed; trailing whitespace or a copy-pasted synonym like 'cached'/'fast'.","commonSituations":"Case mismatches from hand-edited env vars; enum values renamed between releases; synonyms typed from memory instead of copied from docs.","solutions":["Use one of the exact choices listed in the error message (match case exactly).","If the value comes from user input, present the choices from the field metadata instead of a free-text box.","After upgrading headroom, re-check enum choices for renamed values."],"exampleFix":"# before\nsave({'mode': 'Cache'})  # ValueError: 'Cache' not one of ['cache', 'token']\n\n# after\nsave({'mode': 'cache'})","handlingStrategy":"validation","validationCode":"from headroom.settings_store import _BY_KEY\n\ndef valid_choice(key: str, v) -> bool:\n    f = _BY_KEY.get(key)\n    return f is None or str(v) in f.choices","typeGuard":"def is_valid_enum(key: str, v: str) -> bool:\n    f = _BY_KEY.get(key)\n    return f is not None and str(v) in f.choices","tryCatchPattern":"except SettingsValidationError as e:\n    for key, msg in e.field_errors.items():\n        if 'not one of' in msg:\n            choices = _BY_KEY[key].choices\n            payload[key] = next((c for c in choices if c.lower() == str(payload[key]).lower().strip()), payload[key])\n    store.save(payload)","preventionTips":["Build dropdowns from field.choices, never free text.","Lowercase-and-strip user input before matching enum choices."],"tags":["settings","validation","enum"],"backgroundTag":null,"analyzedSha":"322425c43bffde1ed0b64fecf3cf5951565dd82b","analyzedAt":"2026-08-15T01:03:05.481Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}