lfnovo/open-notebook · warning · InvalidInputError

{field} is required and cannot be cleared, only reassigned

Error message

{field} is required and cannot be cleared, only reassigned

What it means

InvalidInputError (mapped to 400) from PATCH/PUT /api/v1/models/defaults when a request explicitly sets one of the REQUIRED_DEFAULTS fields to null. Required defaults (e.g. chat_model) may be reassigned but never cleared, since the app cannot function without them.

Source

Thrown at api/routers/models.py:358

async def update_default_models(defaults_data: DefaultModelsResponse):
    """Update default model assignments.

    Partial-update semantics keyed on field PRESENCE, not value: a field
    absent from the payload is left untouched, while an explicit null clears
    the default (except required ones). `is not None` checks would silently
    ignore a null sent to clear a default — the old value survived while the
    client saw success (same anti-pattern fixed for credentials in #1046).
    """
    try:
        defaults = await DefaultModels.get_instance()

        sent = defaults_data.model_fields_set
        for field in DefaultModelsResponse.model_fields:
            if field not in sent:
                continue
            value = getattr(defaults_data, field)
            if value is None and field in REQUIRED_DEFAULTS:
                raise InvalidInputError(
                    f"{field} is required and cannot be cleared, only reassigned"
                )
            setattr(defaults, field, value)

        await defaults.update()

        # No cache refresh needed - next access will fetch fresh data from DB

        return DefaultModelsResponse(
            default_chat_model=defaults.default_chat_model,  # type: ignore[attr-defined]
            default_transformation_model=defaults.default_transformation_model,  # type: ignore[attr-defined]
            large_context_model=defaults.large_context_model,  # type: ignore[attr-defined]
            default_text_to_speech_model=defaults.default_text_to_speech_model,  # type: ignore[attr-defined]
            default_speech_to_text_model=defaults.default_speech_to_text_model,  # type: ignore[attr-defined]
            default_embedding_model=defaults.default_embedding_model,  # type: ignore[attr-defined]
            default_tools_model=defaults.default_tools_model,  # type: ignore[attr-defined]
        )
    except HTTPException:

View on GitHub (pinned to a7de90d38a)

Solutions

  1. Send a different valid model id instead of null for required fields
  2. Omit required fields from the patch body when not changing them
  3. In the client, disable 'clear' for required defaults and only allow reassignment

Example fix

// before
{"chat_model": null, "embedding_model": "text-embedding-3-small"}
// after
{"chat_model": "gpt-4o-mini", "embedding_model": "text-embedding-3-small"}
Defensive patterns

Strategy: validation

Validate before calling

const REQUIRED = ['chat_model']; // subset of REQUIRED_DEFAULTS
for (const k of REQUIRED) if (patch[k] === null) delete patch[k]; // or reject
await api.updateDefaults(patch);

Type guard

const hasNullRequired = (p: Record<string,string|null>, req: string[]) => req.some(k => p[k] === null);

Try / catch

try { await api.updateDefaults(patch); } catch (e) { if (e.status === 400 && /cannot be cleared/.test(e.detail)) promptReassign(); }

Prevention

When it happens

Trigger: PATCH /models/defaults with {"chat_model": null} or a partial body that includes a required field as null.

Common situations: Frontend sending the whole form with empty selections; generic 'clear settings' UI flow; JSON serializers that emit null for empty inputs.

Related errors


AI-assisted analysis of lfnovo/open-notebook@a7de90d38a (2026-08-27). Data as JSON: /api/errors/45060aaa2d084a03. Report an issue: GitHub.