Skip to content

fix(model): infer provider from allowlist for bare model IDs to prevent prefix drift#51580

Merged
steipete merged 2 commits into
openclaw:mainfrom
honwee:fix/48369-model-provider-prefix-drift
Apr 4, 2026
Merged

fix(model): infer provider from allowlist for bare model IDs to prevent prefix drift#51580
steipete merged 2 commits into
openclaw:mainfrom
honwee:fix/48369-model-provider-prefix-drift

Conversation

@honwee

@honwee honwee commented Mar 21, 2026

Copy link
Copy Markdown

Fixes #48369
Also related: #48096, #48128, #48224, #48256 (5th reported instance of this bug)

Problem

When switching models across different providers via the Control UI (WebChat), bare model IDs (without provider/ prefix) get resolved against the session's current default provider instead of the correct one from the configured allowlist.

Example: if the session default is openai-codex/gpt-5.4 and you switch to kimi-k2.5, it incorrectly resolves to openai-codex/kimi-k2.5 instead of opencode-go/kimi-k2.5, then fails with model not allowed.

Root Cause

resolveAllowedModelRef() in model-selection.ts passes defaultProvider: resolvedDefault.provider (the session's current provider) to resolveModelRefFromString(). When the model string has no /, parseModelRef() applies this wrong default provider.

Fix

When the model string has no /, use the existing inferUniqueProviderFromConfiguredModels() to find the correct provider from the allowlist before falling back to the session's current default provider.

Test

pnpm test -- src/agents/model-selection.test.ts -t 'prefix drift'
✓ 1 test passed

pnpm test -- src/gateway/sessions-patch.test.ts
✓ 27 tests passed

@openclaw-barnacle openclaw-barnacle Bot added agents Agent runtime and tooling size: XS labels Mar 21, 2026
@greptile-apps

greptile-apps Bot commented Mar 21, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes a provider prefix drift bug in resolveAllowedModelRef where a bare model ID (no provider/ prefix) was always resolved against the session's current default provider instead of the correct provider from the configured allowlist.

The fix is minimal and targeted: before passing defaultProvider to resolveModelRefFromString, it calls the existing inferUniqueProviderFromConfiguredModels helper to find the unique allowlist provider for the bare model ID, falling back to the original session default only when the result is ambiguous (same model name under multiple providers) or not found. Alias resolution is unaffected since it runs before defaultProvider is applied inside resolveModelRefFromString.

Key changes:

One minor gap: the tie-breaking fallback path (where the same bare model ID matches multiple allowlist providers and the session default is used) has no dedicated test, leaving a small surface for silent regressions if inferUniqueProviderFromConfiguredModels is refactored later.

Confidence Score: 5/5

  • This PR is safe to merge — the fix is narrow, well-reasoned, and covered by a targeted regression test.
  • The change is a single logical insertion of ~8 lines that uses an already-tested helper (inferUniqueProviderFromConfiguredModels). All edge cases (alias resolution, ambiguous providers, model IDs with /, empty allowlist) either fall through to the original behaviour or are handled correctly by the existing function's undefined return. No data mutation or API surface changes. The only open item is an untested tie-breaking path, which is a P2 suggestion that does not affect the primary fix or cause a production regression.
  • No files require special attention.
Prompt To Fix All With AI
This is a comment left during a code review.
Path: src/agents/model-selection.test.ts
Line: 490-516

Comment:
**Consider adding a test for the ambiguous case**

The fix relies on `inferUniqueProviderFromConfiguredModels` returning `undefined` when the same bare model ID exists under more than one allowlist provider, and then falling back to `params.defaultProvider`. This fallback path isn't exercised by the new test, so a future refactor of `inferUniqueProviderFromConfiguredModels` could silently break it.

A test that configures the same model under two providers and asserts that the session's `defaultProvider` wins would close this gap and document the intended tie-breaking behaviour.

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

Last reviewed commit: "fix(model): infer pr..."

Comment on lines +490 to +516
it("infers provider from allowlist for bare model ids to prevent prefix drift (#48369)", () => {
const cfg = {
agents: {
defaults: {
models: {
"openai-codex/gpt-5.4": {},
"opencode-go/kimi-k2.5": {},
"opencode-go/glm-5": {},
},
},
},
} as OpenClawConfig;

// When session default is openai-codex, switching to a bare "kimi-k2.5"
// should resolve to opencode-go/kimi-k2.5, not openai-codex/kimi-k2.5
const result = resolveAllowedModelRef({
cfg,
catalog: [],
raw: "kimi-k2.5",
defaultProvider: "openai-codex", // session's current provider
});

expect(result).toEqual({
key: "opencode-go/kimi-k2.5",
ref: { provider: "opencode-go", model: "kimi-k2.5" },
});
});

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.

P2 Consider adding a test for the ambiguous case

The fix relies on inferUniqueProviderFromConfiguredModels returning undefined when the same bare model ID exists under more than one allowlist provider, and then falling back to params.defaultProvider. This fallback path isn't exercised by the new test, so a future refactor of inferUniqueProviderFromConfiguredModels could silently break it.

A test that configures the same model under two providers and asserts that the session's defaultProvider wins would close this gap and document the intended tie-breaking behaviour.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/agents/model-selection.test.ts
Line: 490-516

Comment:
**Consider adding a test for the ambiguous case**

The fix relies on `inferUniqueProviderFromConfiguredModels` returning `undefined` when the same bare model ID exists under more than one allowlist provider, and then falling back to `params.defaultProvider`. This fallback path isn't exercised by the new test, so a future refactor of `inferUniqueProviderFromConfiguredModels` could silently break it.

A test that configures the same model under two providers and asserts that the session's `defaultProvider` wins would close this gap and document the intended tie-breaking behaviour.

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

@steipete steipete force-pushed the fix/48369-model-provider-prefix-drift branch from 49f3f39 to b33a928 Compare April 4, 2026 08:30
@steipete steipete force-pushed the fix/48369-model-provider-prefix-drift branch from b33a928 to fac0a48 Compare April 4, 2026 08:30
@steipete steipete merged commit 69980e8 into openclaw:main Apr 4, 2026
9 checks passed

steipete commented Apr 4, 2026

Copy link
Copy Markdown
Contributor

Landed on main.

  • Landed commit: fac0a487f0ffbf18d3c3993f2b351cc9a3d1e72f
  • Merge commit: 69980e8bf48dbc8924f5e86f6e39fbe2bc3ec00e

Thanks @honwee.

KimGLee pushed a commit to KimGLee/openclaw that referenced this pull request Apr 4, 2026
lovewanwan pushed a commit to lovewanwan/openclaw that referenced this pull request Apr 28, 2026
ogt-redknie pushed a commit to ogt-redknie/OPENX that referenced this pull request May 2, 2026
github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request May 9, 2026
github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request May 24, 2026
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: XS

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Model switching reuses the current/default provider prefix across different providers, causing model not allowed

2 participants