Skip to content

fix: prefer configured provider when default provider is removed from config#38947

Closed
davidemanuelDEV wants to merge 1 commit intoopenclaw:mainfrom
davidemanuelDEV:fix/stale-default-model-status
Closed

fix: prefer configured provider when default provider is removed from config#38947
davidemanuelDEV wants to merge 1 commit intoopenclaw:mainfrom
davidemanuelDEV:fix/stale-default-model-status

Conversation

@davidemanuelDEV
Copy link
Copy Markdown
Contributor

Summary

When no explicit model is set in agents.defaults.model, resolveConfiguredModelRef falls back to the hardcoded default (anthropic/claude-opus-4-6). If the user has removed anthropic from models.providers and configured a different provider, openclaw status and openclaw models status --json still report the stale anthropic model as the default — even though runtime sessions correctly use the configured provider.

Root Cause

resolveConfiguredModelRef() unconditionally returns { provider: defaultProvider, model: defaultModel } when no explicit model is configured, without checking whether the default provider is actually available.

Fix

Before returning the hardcoded fallback, the function now checks whether the default provider exists in cfg.models.providers. If it does not, but another provider with models is configured, it returns that provider's first model instead.

This ensures:

  • openclaw status shows the correct effective default model
  • openclaw models status --json reports accurate defaultModel and resolvedDefault
  • missingProvidersInUse no longer falsely flags removed providers

Tests

Added 3 new test cases to model-selection.test.ts:

  • Prefers configured custom provider when default provider is not in models.providers
  • Keeps default provider when it IS in models.providers
  • Falls back to hardcoded default when no custom providers have models

All 43 tests in model-selection.test.ts pass.

Fixes #38880

… config

When no explicit model is set in agents.defaults.model, resolveConfiguredModelRef
falls back to the hardcoded default (anthropic/claude-opus-4-6). If the user has
removed anthropic from models.providers and configured a different provider,
status commands still report the stale anthropic model as the default.

Now, before returning the hardcoded fallback, the function checks whether the
default provider is present in cfg.models.providers. If it is not but another
provider with models is configured, it returns that provider's first model
instead. This ensures openclaw status and openclaw models status --json report
the actual effective default model, not a stale reference to a removed provider.

Fixes openclaw#38880
@openclaw-barnacle openclaw-barnacle bot added agents Agent runtime and tooling size: S labels Mar 7, 2026
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps bot commented Mar 7, 2026

Greptile Summary

This PR adds a guard in resolveConfiguredModelRef so that when no explicit model is configured and the hardcoded default provider (e.g. anthropic) has been removed from models.providers, the function returns the first model from an actually-configured provider instead of the stale default. This fixes misleading output from openclaw status and openclaw models status --json.

The approach is reasonable, but two normalization gaps in the new code can produce incorrect behavior:

  • Raw provider-key lookup (configuredProviders[params.defaultProvider]): The rest of the codebase uses findNormalizedProviderValue / findNormalizedProviderKey for all provider lookups because provider IDs go through normalizeProviderId (handling aliases like "z.ai""zai", case folding, etc.). The direct bracket-access check will return undefined for aliased or differently-cased provider names, causing the function to falsely conclude the default provider is absent and fall back to a different one.
  • Non-normalized provider name returned: providerName is the raw config key, while every other ModelRef returned by this function uses a normalized provider ID. Returning a raw key can silently break downstream provider comparisons (catalog lookups, auth, allowlist checks).

Three new tests cover the happy-path scenarios but do not exercise provider-ID normalization edge cases, so these regressions are not caught by the test suite.

Confidence Score: 3/5

  • Safe to merge for the common case, but two normalization bugs can silently misbehave for aliased or non-lowercase provider configurations.
  • The fix correctly addresses the reported scenario (exact-match provider key removed from config). However, the provider lookup bypasses the existing findNormalizedProviderValue helper used everywhere else in the file, and the returned provider name is not normalized — both issues can produce wrong results for users with aliased or differently-cased provider IDs in their config. The test coverage is good for the main paths but misses these edge cases.
  • src/agents/model-selection.ts — specifically the new hasDefaultProvider check at line 326 and the return at line 338.

Last reviewed commit: 92a93b3

// from a removed provider. (See #38880)
const configuredProviders = params.cfg.models?.providers;
if (configuredProviders && typeof configuredProviders === "object") {
const hasDefaultProvider = Boolean(configuredProviders[params.defaultProvider]);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Provider lookup bypasses normalization

configuredProviders[params.defaultProvider] is a raw object-key lookup, but provider IDs throughout this codebase are normalized via normalizeProviderId before comparison. For example, a user who writes "z.ai" in their config has that stored as-is, yet the canonical ID is "zai". If the defaultProvider is "anthropic" but the user stored it as "Anthropic" (or any other known alias), this check would return undefined and incorrectly conclude the default provider is absent — then silently switch to a different provider.

The correct check should use the existing findNormalizedProviderValue (or findNormalizedProviderKey) utility that already handles this:

Suggested change
const hasDefaultProvider = Boolean(configuredProviders[params.defaultProvider]);
const hasDefaultProvider = Boolean(findNormalizedProviderValue(configuredProviders, params.defaultProvider));
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/agents/model-selection.ts
Line: 326

Comment:
**Provider lookup bypasses normalization**

`configuredProviders[params.defaultProvider]` is a raw object-key lookup, but provider IDs throughout this codebase are normalized via `normalizeProviderId` before comparison. For example, a user who writes `"z.ai"` in their config has that stored as-is, yet the canonical ID is `"zai"`. If the `defaultProvider` is `"anthropic"` but the user stored it as `"Anthropic"` (or any other known alias), this check would return `undefined` and incorrectly conclude the default provider is absent — then silently switch to a different provider.

The correct check should use the existing `findNormalizedProviderValue` (or `findNormalizedProviderKey`) utility that already handles this:

```suggestion
    const hasDefaultProvider = Boolean(findNormalizedProviderValue(configuredProviders, params.defaultProvider));
```

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

Comment on lines +335 to +338
if (availableProvider) {
const [providerName, providerCfg] = availableProvider;
const firstModel = providerCfg.models[0];
return { provider: providerName, model: firstModel.id };
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Returned provider name is not normalized

providerName is the raw key from the config object (e.g. "z.ai", "Anthropic", "bytedance"), but every other return path in resolveConfiguredModelRef returns a normalized provider ID (via normalizeModelRefnormalizeProviderId). Returning a non-normalized ID here can cause subtle mismatches downstream when callers compare the provider string against catalog entries, auth lookups, or allowlist checks.

The provider name should be normalized before returning:

Suggested change
if (availableProvider) {
const [providerName, providerCfg] = availableProvider;
const firstModel = providerCfg.models[0];
return { provider: providerName, model: firstModel.id };
return { provider: normalizeProviderId(providerName), model: firstModel.id };
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/agents/model-selection.ts
Line: 335-338

Comment:
**Returned provider name is not normalized**

`providerName` is the raw key from the config object (e.g. `"z.ai"`, `"Anthropic"`, `"bytedance"`), but every other return path in `resolveConfiguredModelRef` returns a normalized provider ID (via `normalizeModelRef``normalizeProviderId`). Returning a non-normalized ID here can cause subtle mismatches downstream when callers compare the provider string against catalog entries, auth lookups, or allowlist checks.

The provider name should be normalized before returning:

```suggestion
        return { provider: normalizeProviderId(providerName), model: firstModel.id };
```

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

Copy link
Copy Markdown

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

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: 92a93b3796

ℹ️ 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 +326 to +327
const hasDefaultProvider = Boolean(configuredProviders[params.defaultProvider]);
if (!hasDefaultProvider) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Honor merge mode before switching default provider

This fallback path treats cfg.models.providers as the full provider set, but in OpenClaw models.mode defaults to merge (src/agents/models-config.ts:17,222), where built-in/implicit providers can still exist even if they are not listed under cfg.models.providers. With this check, any user who adds a custom provider in merge mode and leaves agents.defaults.model unset will now silently switch the effective default to that custom provider’s first model, changing runtime defaults and status output unexpectedly; the fallback should only trigger when the effective catalog has actually dropped the default provider (for example, replace mode).

Useful? React with 👍 / 👎.

Copy link
Copy Markdown

@mdlmarkham mdlmarkham left a comment

Choose a reason for hiding this comment

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

Review: Prefer Configured Provider When Default Removed ✅

Verdict: Clean fix with good tests. Ready to merge.

Problem

When users:

  1. Configure a custom provider (e.g., n1n)
  2. Remove the default provider (anthropic) from config
  3. Don't set an explicit model in agents.defaults.model

The status commands still show anthropic/claude-opus-4-6 as default — even though it's unavailable.

Impact:

  • openclaw status shows wrong model
  • openclaw models status --json reports stale default
  • Runtime correctly uses configured provider, but CLI status is misleading

The Fix

// Before: always return hardcoded default
return { provider: defaultProvider, model: defaultModel };

// After: check if default provider exists
if (cfg.models.providers[defaultProvider]) {
  return { provider: defaultProvider, model: defaultModel };
}
// If not, find first configured provider with models
const configuredProvider = Object.keys(cfg.models.providers).find(/* has models */);
return { provider: configuredProvider, model: configuredProvider.models[0].id };

Test Coverage

  • ✅ Provider removed → falls back to first configured provider
  • ✅ Default provider exists → uses default (no regression)
  • ✅ No providers with models → falls back to hardcoded default
  • ✅ Empty provider → skipped (no models)

Edge Cases

  1. Multiple configured providers: Returns first alphabetical provider with models. This is deterministic but arbitrary — operators should set explicit model if order matters.

  2. All providers empty: Falls back to hardcoded default (anthropic/claude-opus-4-6). This is correct — better to show unavailable default than crash.

  3. Provider added after removal: Next status reflects the new provider correctly.

Minor Suggestion

Consider logging when falling back to configured provider vs hardcoded default:

log.debug(`Default provider ${defaultProvider} not in config, using ${configuredProvider}`);

This helps debugging if someone wonders why their status shows a different model than expected.


Recommendation: Approve. Fixes misleading status display, minimal change surface, good tests.

steipete added a commit that referenced this pull request Mar 7, 2026
@steipete
Copy link
Copy Markdown
Contributor

steipete commented Mar 7, 2026

Landed. Thank you @davidemanuelDEV.

What I did:

SHA hashes:

  • original PR commit: 92a93b3796d0
  • landed commit on main: 231c1fa37

Thanks again for the fix.

@steipete steipete closed this Mar 7, 2026
mrosmarin added a commit to mrosmarin/openclaw that referenced this pull request Mar 7, 2026
* main: (133 commits)
  reduce image size, offer slim image (openclaw#38479)
  fix(security): harden install base drift cleanup
  fix(agents): respect explicit provider baseUrl in merge mode (openclaw#39103)
  fix(agents): apply contextTokens cap for compaction threshold (openclaw#39099)
  fix(exec): block dangerous override-only env pivots
  fix(security): stage installs before publish
  fix(daemon): normalise whitespace in checkTokenDrift to prevent false-positive warning (openclaw#39108)
  fix(security): harden fs-safe copy writes
  refactor: dedupe bluebubbles webhook auth test setup
  refactor: dedupe discord native command test scaffolding
  refactor: dedupe anthropic probe target test setup
  refactor: dedupe minimax provider auth test setup
  refactor: dedupe runtime snapshot test fixtures
  fix: harden zip extraction writes
  fix(tests): stabilize diffs localReq headers (supersedes openclaw#39063)
  fix: harden workspace skill path containment
  fix(agents): land openclaw#38935 from @MumuTW
  fix(models): land openclaw#38947 from @davidemanuelDEV
  fix(gateway): land openclaw#39064 from @Narcooo
  fix(models-auth): land openclaw#38951 from @MumuTW
  ...
vincentkoc pushed a commit to BryanTegomoh/openclaw-fork that referenced this pull request Mar 8, 2026
openperf pushed a commit to openperf/moltbot that referenced this pull request Mar 8, 2026
mcaxtr pushed a commit to mcaxtr/openclaw that referenced this pull request Mar 8, 2026
Saitop pushed a commit to NomiciAI/openclaw that referenced this pull request Mar 8, 2026
GordonSH-oss pushed a commit to GordonSH-oss/openclaw that referenced this pull request Mar 9, 2026
jenawant pushed a commit to jenawant/openclaw that referenced this pull request Mar 10, 2026
dhoman pushed a commit to dhoman/chrono-claw that referenced this pull request Mar 11, 2026
senw-developers pushed a commit to senw-developers/va-openclaw that referenced this pull request Mar 17, 2026
V-Gutierrez pushed a commit to V-Gutierrez/openclaw-vendor that referenced this pull request Mar 17, 2026
alexey-pelykh pushed a commit to remoteclaw/remoteclaw that referenced this pull request Mar 21, 2026
(cherry picked from commit 231c1fa)

Upstream files discarded (fork-deleted):
- src/agents/model-selection.test.ts
- src/agents/model-selection.ts
alexey-pelykh pushed a commit to remoteclaw/remoteclaw that referenced this pull request Mar 21, 2026
(cherry picked from commit 231c1fa)

Upstream files discarded (fork-deleted):
- src/agents/model-selection.test.ts
- src/agents/model-selection.ts
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agents Agent runtime and tooling size: S

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: openclaw status / openclaw models status --json report stale default model from removed provider

4 participants