Skip to content

fix: control UI model selector sends correct provider prefix#47581

Merged
steipete merged 2 commits into
openclaw:mainfrom
chrishham:fix/control-ui-model-provider-value
Mar 16, 2026
Merged

fix: control UI model selector sends correct provider prefix#47581
steipete merged 2 commits into
openclaw:mainfrom
chrishham:fix/control-ui-model-provider-value

Conversation

@chrishham

Copy link
Copy Markdown
Contributor

Summary

The Control UI model dropdown sends the wrong provider prefix when switching to a model from a different provider than the session's current one.

Root cause: buildChatModelOptions() in ui/src/ui/app-render.helpers.ts uses just entry.id (e.g. gpt-5.2) as the <option> value, while the label correctly shows gpt-5.2 · openai. When switchChatModel sends this bare model ID to sessions.patch, the server's parseModelRef() sees no / and falls back to the session's current defaultProvider (e.g. anthropic), yielding anthropic/gpt-5.2 instead of openai/gpt-5.2.

Fix:

  • buildChatModelOptions: use provider/model as option values (e.g. openai/gpt-5.2)
  • resolveModelOverrideValue: return provider/model from server session data so selected state stays in sync
  • resolveDefaultModelValue: same treatment for the default model value
  • Add modelProvider to GatewaySessionsDefaults UI type (server already sends it)

Reproduces when: you have models from multiple providers (e.g. Anthropic + OpenAI, Anthropic + Ollama, OpenRouter models) and switch between them in the Control UI.

Related issues

Closes #47559, closes #47352, closes #46764, closes #46577, closes #46453, closes #45938, closes #46859, closes #45630

All report the same root cause: the Control UI model dropdown strips or misassigns the provider prefix.

Test plan

  • Configure models from 2+ providers (e.g. anthropic/claude-sonnet-4-5 and openai/gpt-5.2)
  • Open Control UI, switch model via dropdown
  • Verify sessions.patch sends openai/gpt-5.2 (not anthropic/gpt-5.2)
  • Verify switching back to default works
  • Verify the selected model stays highlighted correctly after page refresh

The model selector was using just the model ID (e.g. "gpt-5.2") as the
option value. When sent to sessions.patch, the server would fall back to
the session's current provider ("anthropic") yielding "anthropic/gpt-5.2"
instead of "openai/gpt-5.2".

Now option values use "provider/model" format, and resolveModelOverrideValue
and resolveDefaultModelValue also return the full provider-prefixed key so
selected state stays consistent.
@greptile-apps

greptile-apps Bot commented Mar 15, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes a long-standing bug where the Control UI model dropdown sent bare model IDs (e.g. gpt-5.2) to sessions.patch, causing the server's parseModelRef() to fall back to the session's current defaultProvider and produce the wrong provider/model pairing.

The fix is well-scoped and internally consistent:

  • buildChatModelOptions now uses provider/model as option value attributes, so the <select> change handler passes the full qualified name to the server.
  • resolveModelOverrideValue and resolveDefaultModelValue are updated to return the same provider/model format, keeping ?selected state in sync across page refreshes.
  • GatewaySessionsDefaults gains the modelProvider field the server was already sending but the client type didn't expose.

Issues found:

  • The defaultLabel in renderChatModelSelect now displays Default (openai/gpt-5.2) using the internal provider/model format, while individual options use the human-friendly gpt-5.2 · openai style — a minor cosmetic inconsistency worth aligning.

Confidence Score: 4/5

  • Safe to merge — the core logic is correct and addresses the reported root cause; only a cosmetic display inconsistency remains.
  • The fix correctly and consistently applies the provider/model format across option values and the two resolver functions. The sessions.patch call will now receive the properly prefixed model ref that parseModelRef() expects. Score is 4 rather than 5 only because the defaultLabel display still exposes the raw provider/model string instead of the user-friendly model · provider format used by the individual options.
  • No files require special attention beyond the cosmetic label issue in app-render.helpers.ts line 595.

Comments Outside Diff (1)

  1. ui/src/ui/app-render.helpers.ts, line 595 (link)

    Default label shows raw provider/model format

    After this fix, defaultModel is now a provider/model string (e.g. openai/gpt-5.2), so the default option label becomes Default (openai/gpt-5.2). The individual option labels use the friendlier gpt-5.2 · openai format, creating a visual inconsistency in the dropdown.

    Consider extracting the display portion from defaultModel for the label, or building a separate display string:

    Or more readably, look up the matching catalog entry to reuse its label format:

    const defaultEntry = (state.chatModelCatalog ?? []).find(
      (e) => (e.provider ? `${e.provider}/${e.id}` : e.id) === defaultModel,
    );
    const defaultLabel = defaultModel
      ? `Default (${defaultEntry ? `${defaultEntry.id} · ${defaultEntry.provider}` : defaultModel})`
      : "Default model";
    

    Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Prompt To Fix All With AI
This is a comment left during a code review.
Path: ui/src/ui/app-render.helpers.ts
Line: 595

Comment:
**Default label shows raw `provider/model` format**

After this fix, `defaultModel` is now a `provider/model` string (e.g. `openai/gpt-5.2`), so the default option label becomes `Default (openai/gpt-5.2)`. The individual option labels use the friendlier `gpt-5.2 · openai` format, creating a visual inconsistency in the dropdown.

Consider extracting the display portion from `defaultModel` for the label, or building a separate display string:

```suggestion
  const defaultLabel = defaultModel
    ? `Default (${defaultModel.includes("/") ? defaultModel.split("/").slice(1).join("/") + " · " + defaultModel.split("/")[0] : defaultModel})`
    : "Default model";
```

Or more readably, look up the matching catalog entry to reuse its label format:

```
const defaultEntry = (state.chatModelCatalog ?? []).find(
  (e) => (e.provider ? `${e.provider}/${e.id}` : e.id) === defaultModel,
);
const defaultLabel = defaultModel
  ? `Default (${defaultEntry ? `${defaultEntry.id} · ${defaultEntry.provider}` : defaultModel})`
  : "Default model";
```

How can I resolve this? If you propose a fix, please make it concise.

Last reviewed commit: 5769b05

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5769b05277

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +574 to +575
const value = provider ? `${provider}/${entry.id}` : entry.id;
addOption(value, provider ? `${entry.id} · ${provider}` : entry.id);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Normalize cached override values before comparing option keys

The new provider/model option values here assume currentOverride is also provider-qualified, but resolveModelOverrideValue still returns cached chatModelOverrides[sessionKey] verbatim; that cache is populated by /model with raw user input (often bare model IDs). In that flow, the select value (gpt-5.2) no longer matches catalog values (openai/gpt-5.2), so a synthetic bare option is injected and the picker can drift from server-resolved state until reload. Normalizing cached overrides to the same key format (or updating cache from resolved patch data) would keep selection behavior consistent.

Useful? React with 👍 / 👎.

The default option showed 'Default (openai/gpt-5.2)' while individual
options used the friendlier 'gpt-5.2 · openai' format.
@steipete steipete merged commit d9fb50e into openclaw:main Mar 16, 2026
29 of 32 checks passed
@steipete

Copy link
Copy Markdown
Contributor

Landed via temp rebase onto main.

  • Gate: pnpm check && pnpm build && pnpm test && pnpm check:docs
  • Land commit: 931c040
  • Merge commit: d9fb50e

Thanks @chrishham!

@chrishham chrishham deleted the fix/control-ui-model-provider-value branch March 16, 2026 05:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment