odysseus-dev/odysseus · error · HTTPException

Selected model endpoint was removed. Pick another model in…

Error message

Selected model endpoint was removed. Pick another model in Settings.

What it means

The session references an endpoint (model provider route) that no longer exists; _clear_orphaned_session_endpoint detected the endpoint was deleted, cleared it from the session, and the handler rejects with 400 telling the user to re-pick a model. This prevents calls to a dead upstream URL.

Solutions

  1. Open Settings in the client, pick a live endpoint/model for this chat, then resend
  2. If the endpoint should exist, re-create it in endpoint management and re-select it
  3. Administrators: warn users before deleting endpoints that sessions depend on
Defensive patterns

Strategy: validation

Validate before calling

const endpoints = await fetch('/api/endpoints').then(r=>r.json());
if (!endpoints.some(ep => ep.id === session.endpoint_id)) { promptEndpointRebind(); }

Try / catch

try { await send(...); } catch (e) { if (e.status === 400 && /endpoint was removed/.test(e.message)) openSettingsForModelPick(); else throw e; }

Prevention

When it happens

Trigger: Endpoint deleted or re-created in Settings after the session was bound to it; session restored from backup referencing an endpoint ID that was removed; multi-admin environments where one admin prunes endpoints.

Common situations: User had a chat open on a custom OpenAI-compatible endpoint that was later deleted; migration or cleanup scripts removed endpoint records while sessions still pointed at them.

Related errors


AI-assisted analysis of odysseus-dev/odysseus@f9235ebbf1 (2026-08-14). Data as JSON: /api/errors/421bc5dd8e0adfe8. Report an issue: GitHub.

Appendix: source

Thrown at routes/chat_routes.py:700

        message = chat_request.message
        session = chat_request.session
        att_ids = chat_request.attachments or []
        use_web = chat_request.use_web
        use_research = chat_request.use_research
        time_filter = chat_request.time_filter
        preset_id = chat_request.preset_id

        # Verify the caller owns this session before loading it.
        # Without this, any authenticated user can post into another user's chat.
        _verify_session_owner(request, session)

        try:
            sess = session_manager.get_session(session)
        except KeyError:
            raise HTTPException(404, f"Session '{session}' not found")
        owner = effective_user(request)
        if _clear_orphaned_session_endpoint(sess, owner=owner):
            raise HTTPException(400, "Selected model endpoint was removed. Pick another model in Settings.")

        # Empty model + live endpoint = setup race (Issue #587). Repair from
        # the endpoint's cached model list before privilege checks, which
        # otherwise see "" and behave inconsistently with the allowlist.
        _recover_empty_session_model(sess, session, owner=owner)
        if not getattr(sess, "model", "").strip():
            raise HTTPException(
                400,
                "No model selected for this chat. Open the model picker and choose one before sending.",
            )
        if not (getattr(sess, "endpoint_url", "") or "").strip():
            raise HTTPException(400, "Selected model endpoint is not configured")

        # Same allowed_models + daily-cap gate as chat_stream (mirror so the
        # non-streaming path can't be used to bypass).
        _enforce_chat_privileges(request, sess)

        tool_policy = build_effective_tool_policy(last_user_message=message)

View on GitHub (pinned to f9235ebbf1)