headroomlabs-ai/headroom · error · SettingsValidationError

settings validation failed: unknown={unknown_keys} errors={f

Error message

settings validation failed: unknown={unknown_keys} errors={field_errors}

What it means

SettingsValidationError is the aggregate error raised when a settings payload fails validation: unknown_keys lists keys not in the registry and field_errors maps each bad field key to its _coerce message (bool/number/range/enum/header-map failures). It carries structured detail (attributes unknown_keys, field_errors) precisely so the API layer can map unknown keys to HTTP 400 and field errors to 422. The f-string at line 729/848 renders both into the message.

Source

Thrown at headroom/settings_store.py:848

    fails coercion. Returns the coerced dict (``None`` values dropped) on success.
    """
    values = _normalize_values(values)
    unknown = [key for key in values if key not in _BY_KEY]
    field_errors: dict[str, str] = {}
    coerced: dict[str, Any] = {}
    for key, value in values.items():
        field = _BY_KEY.get(key)
        if field is None:
            continue
        try:
            result = _coerce(field, value)
        except (ValueError, TypeError) as exc:
            field_errors[key] = str(exc)
            continue
        if result is not None:
            coerced[key] = result
    if unknown or field_errors:
        raise SettingsValidationError(unknown, field_errors)
    return coerced


def load() -> dict[str, Any]:
    """Return validated stored values. Fail-open: ``{}`` if missing or corrupt."""
    path = paths.settings_path()
    try:
        raw = path.read_text(encoding="utf-8")
    except FileNotFoundError:
        return {}
    except OSError as exc:
        logger.warning("settings_store: cannot read %s: %s", path, exc)
        return {}
    try:
        data = json.loads(raw)
    except (ValueError, UnicodeDecodeError) as exc:
        logger.warning("settings_store: ignoring corrupt settings.json: %s", exc)
        return {}

View on GitHub (pinned to 322425c43b)

Solutions

  1. Read the two lists in the message: fix or remove every key in unknown=, and correct each field in errors= using its per-field message.
  2. If keys are unknown because of a version rename, update the client to the current key names in headroom.settings_store.SETTINGS.
  3. Programmatically iterate exc.unknown_keys / exc.field_errors instead of parsing the message string.

Example fix

# before
save({'proxy_mod': 'cache', 'max_retries': -1})  # SettingsValidationError

# after
save({'proxy_mode': 'cache', 'max_retries': 0})
Defensive patterns

Strategy: try-catch

Validate before calling

from headroom.settings_store import _BY_KEY, SETTINGS

def prevalidate(payload: dict) -> tuple[list, dict]:
    unknown = [k for k in payload if k not in _BY_KEY]
    errors = {}
    for k, v in payload.items():
        f = _BY_KEY.get(k)
        if f is None:
            continue
        try:
            _coerce(f, v)  # or replicate the checks per type
        except ValueError as e:
            errors[k] = str(e)
    return unknown, errors

Try / catch

from headroom.settings_store import SettingsValidationError
try:
    store.save(payload)
except SettingsValidationError as e:
    bad = set(e.unknown_keys) | set(e.field_errors)
    payload = {k: v for k, v in payload.items() if k not in bad}
    store.save(payload)  # retry with clean subset; log what was dropped

Prevention

When it happens

Trigger: Calling the save/validate path with a payload containing an unrecognized key (typo like 'proxy_mod') or any invalid value that _coerce rejects (see errors 362-370). Also raised directly by _normalize_values when env aliases conflict (error 361).

Common situations: First-time integrations guessing setting names; stale clients from older versions sending renamed keys; bulk config imports where one bad row should not abort everything.

Related errors


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