headroomlabs-ai/headroom · error · ValueError

expected a JSON object of header name/value strings

Error message

expected a JSON object of header name/value strings

What it means

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.

Source

Thrown at headroom/settings_store.py:808

        return number
    if field.type == "enum":
        token = str(value)
        if token not in field.choices:
            raise ValueError(f"{token!r} not one of {list(field.choices)}")
        return token
    if field.type == "csv-list":
        tokens = value if isinstance(value, list | tuple) else str(value).split(",")
        tokens = [str(token).strip() for token in tokens]
        tokens = [token for token in tokens if token]
        return ",".join(tokens) if tokens else None
    if field.type == "header-map":
        if isinstance(value, dict):
            parsed = value
        else:
            try:
                parsed = json.loads(str(value))
            except (ValueError, TypeError) as exc:
                raise ValueError("expected a JSON object of header name/value strings") from exc
        if not isinstance(parsed, dict) or not all(
            isinstance(k, str) and isinstance(v, str) for k, v in parsed.items()
        ):
            raise ValueError("expected a JSON object of header name/value strings")
        return json.dumps(parsed, sort_keys=True) if parsed else None
    # str
    token = str(value)
    return token if token != "" else None


def _serialize(field: SettingField, value: Any) -> str:
    """Serialize a coerced value to the exact string its env var expects."""
    if field.type in ("bool", "optional-bool"):
        return "1" if value else "0"
    return str(value)


def validate(values: dict[str, Any]) -> dict[str, Any]:

View on GitHub (pinned to 322425c43b)

Solutions

  1. Supply a valid JSON object: '{"X-Header": "value"}' — keys and values both strings.
  2. Single-quote the whole env value in shell so inner double quotes survive.
  3. Validate with json.loads in a pre-deploy check so shell mangling is caught before startup.

Example fix

# before
OPENAI_TARGET_API_HEADERS="Authorization: Bearer tkn"  # not JSON

# after
OPENAI_TARGET_API_HEADERS='{"Authorization": "Bearer tkn"}'
Defensive patterns

Strategy: validation

Validate before calling

import json

def parseable_header_map(v) -> bool:
    if isinstance(v, dict):
        return True
    try:
        json.loads(str(v))
        return True
    except (ValueError, TypeError):
        return False

Try / catch

except SettingsValidationError as e:
    for key, msg in e.field_errors.items():
        if 'header' in msg:
            raise SystemExit(f'{key} must be a JSON object of headers, got: {payload[key]!r}')

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of headroomlabs-ai/headroom@322425c43b (2026-08-15). Data as JSON: /api/errors/f6a8c1553d919c75. Report an issue: GitHub.