{"record":{"id":"f6a8c1553d919c75","repo":"headroomlabs-ai/headroom","slug":"expected-a-json-object-of-header-name-value-string","errorCode":null,"errorMessage":"expected a JSON object of header name/value strings","messagePattern":"expected a JSON object of header name/value strings","errorType":"validation","errorClass":"ValueError","httpStatus":422,"severity":"error","filePath":"headroom/settings_store.py","lineNumber":808,"sourceCode":"        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\")\n        return json.dumps(parsed, sort_keys=True) if parsed else None\n    # str\n    token = str(value)\n    return token if token != \"\" else None\n\n\ndef _serialize(field: SettingField, value: Any) -> str:\n    \"\"\"Serialize a coerced value to the exact string its env var expects.\"\"\"\n    if field.type in (\"bool\", \"optional-bool\"):\n        return \"1\" if value else \"0\"\n    return str(value)\n\n\ndef validate(values: dict[str, Any]) -> dict[str, Any]:","sourceCodeStart":790,"sourceCodeEnd":826,"githubUrl":"https://github.com/headroomlabs-ai/headroom/blob/322425c43bffde1ed0b64fecf3cf5951565dd82b/headroom/settings_store.py#L790-L826","documentation":"ValueError from _coerce in headroom/settings_store.py when a 'header-map' field receives a non-dict value that is not even parseable JSON (json.loads raises ValueError/TypeError, line 808). This guards settings like OPENAI_TARGET_API_HEADERS / openai_extra_headers, which must be a JSON object of header-name → header-value strings. The original JSON parse error is chained (__cause__). Reported per-field via SettingsValidationError.field_errors.","triggerScenarios":"save({'openai_extra_headers': 'Authorization: Bearer x'}) — a header-line syntax instead of JSON; a single quoted header value; truncated JSON from env-var length limits; unquoted braces in a shell variable.","commonSituations":"Operators used to curl -H syntax pasting header lines into the setting; env vars mangled by quoting/escaping in docker-compose or Kubernetes YAML (the $ and quotes break JSON); multi-line values collapsed by .env parsers.","solutions":["Supply a valid JSON object: '{\"X-Header\": \"value\"}' — keys and values both strings.","Single-quote the whole env value in shell so inner double quotes survive.","Validate with json.loads in a pre-deploy check so shell mangling is caught before startup."],"exampleFix":"# before\nOPENAI_TARGET_API_HEADERS=\"Authorization: Bearer tkn\"  # not JSON\n\n# after\nOPENAI_TARGET_API_HEADERS='{\"Authorization\": \"Bearer tkn\"}'","handlingStrategy":"validation","validationCode":"import json\n\ndef parseable_header_map(v) -> bool:\n    if isinstance(v, dict):\n        return True\n    try:\n        json.loads(str(v))\n        return True\n    except (ValueError, TypeError):\n        return False","typeGuard":null,"tryCatchPattern":"except SettingsValidationError as e:\n    for key, msg in e.field_errors.items():\n        if 'header' in msg:\n            raise SystemExit(f'{key} must be a JSON object of headers, got: {payload[key]!r}')","preventionTips":["Test header-map env vars with json.loads in CI.","Prefer writing the setting through the API/JSON store (a dict) over raw env strings."],"tags":["settings","json","headers"],"backgroundTag":null,"analyzedSha":"322425c43bffde1ed0b64fecf3cf5951565dd82b","analyzedAt":"2026-08-15T01:03:05.481Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}