{"record":{"id":"9f8f31ab79d8bc48","repo":"headroomlabs-ai/headroom","slug":"conflicting-values-supplied-for-source-keys-key","errorCode":null,"errorMessage":"conflicting values supplied for {source_keys[key]!r} and {incoming_key!r}","messagePattern":"conflicting values supplied for (.+?) and (.+?)","errorType":"validation","errorClass":"SettingsValidationError","httpStatus":422,"severity":"error","filePath":"headroom/settings_store.py","lineNumber":750,"sourceCode":"\ndef _normalize_values(values: dict[str, Any]) -> dict[str, Any]:\n    \"\"\"Rewrite known env aliases to their JSON/API keys.\"\"\"\n    normalized: dict[str, Any] = {}\n    source_keys: dict[str, str] = {}\n    conflicts: dict[str, str] = {}\n    for incoming_key, value in values.items():\n        field = _BY_ENV.get(incoming_key)\n        key = field.key if field is not None else incoming_key\n        if key in normalized:\n            if normalized[key] != value:\n                conflicts[key] = (\n                    f\"conflicting values supplied for {source_keys[key]!r} and {incoming_key!r}\"\n                )\n            continue\n        normalized[key] = value\n        source_keys[key] = incoming_key\n    if conflicts:\n        raise SettingsValidationError([], conflicts)\n    return normalized\n\n\ndef _coerce(field: SettingField, value: Any) -> Any:\n    \"\"\"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","sourceCodeStart":732,"sourceCodeEnd":768,"githubUrl":"https://github.com/headroomlabs-ai/headroom/blob/322425c43bffde1ed0b64fecf3cf5951565dd82b/headroom/settings_store.py#L732-L768","documentation":"Raised as SettingsValidationError from _normalize_values in headroom/settings_store.py when the same logical setting is supplied twice through two different keys — typically its environment-variable alias (e.g. OPENAI_TARGET_API_HEADERS) and its JSON/API key (openai_extra_headers) — with values that differ. The message names both the first-seen source key and the conflicting incoming key. Equal duplicate values are fine; only mismatched values conflict.","triggerScenarios":"Saving settings with a payload that contains both the env alias and the canonical key for one field, e.g. {'HEADROOM_PROXY_MODE': 'cache', 'proxy_mode': 'token'}. Also merging a dotenv file (env-style keys) with an API payload (JSON keys) where one side was updated but not the other.","commonSituations":"Scripts that load .env into a dict and then merge settings fetched from the settings API; migration code that writes canonical keys while legacy env names linger; copy-pasted config blocks that keep both spellings with drifted values.","solutions":["Pick ONE spelling per field — prefer the canonical JSON/API key — and delete the env-alias duplicate.","If both must coexist temporarily, make the values literally identical (equal duplicates are accepted).","Re-run the save; if it still conflicts, inspect the offending keys named in the message and align them."],"exampleFix":"# before\nvalues = {'HEADROOM_PROXY_MODE': 'cache', 'proxy_mode': 'token'}\nsave(values)  # SettingsValidationError: conflicting values\n\n# after\nvalues = {'proxy_mode': 'token'}\nsave(values)","handlingStrategy":"validation","validationCode":"from headroom.settings_store import _BY_ENV  # env alias -> SettingField\n\ndef has_alias_conflicts(values: dict) -> dict[str, str]:\n    seen = {}\n    for k, v in values.items():\n        canon = _BY_ENV[k].key if k in _BY_ENV else k\n        if canon in seen and seen[canon] != v:\n            return {canon: f'{canon} supplied twice with different values'}\n        seen[canon] = v\n    return {}","typeGuard":null,"tryCatchPattern":"from headroom.settings_store import SettingsValidationError\ntry:\n    store.save(payload)\nexcept SettingsValidationError as e:\n    # e.field_errors maps the canonical key -> conflict message\n    for key, msg in e.field_errors.items():\n        print(f'{key}: {msg}')  # drop one of the two spellings","preventionTips":["Never merge env-alias keys and canonical keys into one payload; normalize first.","Keep settings in one source of truth (the JSON store) and derive env vars from it."],"tags":["settings","configuration","conflict"],"backgroundTag":null,"analyzedSha":"322425c43bffde1ed0b64fecf3cf5951565dd82b","analyzedAt":"2026-08-15T01:03:05.481Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}