Skip to content

fix(cli): respect OPENAI_MODEL precedence in CLI model resolution#3567

Merged
wenshao merged 4 commits into
QwenLM:mainfrom
B-A-M-N:fix/ollama-openai-model-precedence
Apr 24, 2026
Merged

fix(cli): respect OPENAI_MODEL precedence in CLI model resolution#3567
wenshao merged 4 commits into
QwenLM:mainfrom
B-A-M-N:fix/ollama-openai-model-precedence

Conversation

@B-A-M-N

@B-A-M-N B-A-M-N commented Apr 23, 2026

Copy link
Copy Markdown
Contributor

Summary

Fix CLI-side model resolution precedence for OpenAI-compatible auth so env-driven model selection stays aligned with provider lookup.

resolveCliGenerationConfig() now uses this order when selecting the requested model:

  • argv.model
  • OPENAI_MODEL
  • QWEN_MODEL
  • settings.model.name

Why

For OpenAI-compatible integrations, model selection can be injected through OPENAI_MODEL. Without this precedence in the CLI resolver, the final resolved model and modelProviders lookup can drift from the env-selected model and fall back to settings/defaults instead.

This change keeps those paths aligned.

Changes

Files:

  • packages/cli/src/utils/modelConfigUtils.ts
  • packages/cli/src/utils/modelConfigUtils.test.ts

Implementation:

  • apply argv.model -> OPENAI_MODEL -> QWEN_MODEL -> settings.model.name when choosing the requested model for provider lookup

Tests:

  • cover provider lookup from OPENAI_MODEL when argv.model is absent
  • cover OPENAI_MODEL taking precedence over QWEN_MODEL
  • cover end-result precedence across CLI, env, and settings inputs

Scope

This PR is intentionally narrow.

It does not include:

  • UI changes
  • lockfile changes
  • unrelated core/runtime changes
  • standalone repro-only test files

Verification

Ran targeted canonical suites locally:

  • npm test --workspace=packages/cli -- --run src/utils/modelConfigUtils.test.ts
  • npm test --workspace=packages/core -- --run src/models/modelConfigResolver.test.ts

These passed against the development checkout used to produce this patch.

const envQwenModel = env['QWEN_MODEL'];

const requestedModel =
argv.model || envOpenAIModel || envQwenModel || settings.model?.name;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Critical] OPENAI_MODEL and QWEN_MODEL are now used for provider lookup for every auth type, not just USE_OPENAI. That lets unrelated env vars influence modelProvider selection for Anthropic/Gemini/Vertex flows, while resolveModelConfig() still uses auth-specific env mappings. This can cause provider-scoped settings to come from the wrong provider.

Please gate this fallback to AuthType.USE_OPENAI, or derive the model env var from the selected auth type instead of hardcoding OpenAI/Qwen env vars.

— gpt-5.4 via Qwen Code /review

selectedAuthType: AuthType.USE_OPENAI,
env: { OPENAI_MODEL: 'openai-env-model' },
});
expect(result1.model).toBe('cli-model');

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Suggestion] This test only asserts the mocked resolveModelConfig() return value, so it does not verify which modelProvider was selected before delegation. A precedence regression here could return without this test failing.

Please assert on the modelProvider argument passed to resolveModelConfig() (and ideally add a non-USE_OPENAI auth case as well).

— gpt-5.4 via Qwen Code /review

@B-A-M-N

B-A-M-N commented Apr 24, 2026

Copy link
Copy Markdown
Contributor Author

@wenshao Fixed in 124b20a. I updated the tests to assert the delegated modelProvider directly, and added a regression case showing OPENAI_MODEL does not affect non-USE_OPENAI provider lookup.

@@ -723,5 +860,71 @@ describe('modelConfigUtils', () => {
}),
);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Suggestion] The test name says it validates the full precedence chain argv.model > OPENAI_MODEL > QWEN_MODEL > settings.model.name, but the added cases never exercise the QWEN_MODEL-only fallback path. They cover CLI override, OPENAI_MODEL over QWEN_MODEL, OPENAI_MODEL alone, and settings fallback, but not the branch where OPENAI_MODEL is absent and QWEN_MODEL should select the provider.

A regression that removes or reorders the QWEN_MODEL fallback would not be caught despite this test claiming to cover the full precedence chain. Please add a case with env: { QWEN_MODEL: 'qwen-env-model' } and no OPENAI_MODEL, expecting qwen-env-model, or narrow the test name to match the cases actually covered.

— gpt-5.5 via Qwen Code /review

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good catch. I missed the QWEN_MODEL-only fallback branch. I’ll add a regression test for env: { QWEN_MODEL: ... } with no OPENAI_MODEL so the suite actually covers the full precedence chain.

@B-A-M-N B-A-M-N requested a review from wenshao April 24, 2026 15:47

@wenshao wenshao left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

No issues found. LGTM! ✅ — gpt-5.5 via Qwen Code /review

@wenshao

wenshao commented Apr 24, 2026

Copy link
Copy Markdown
Collaborator

tmux end-to-end verification

Called the real resolveModelConfig against the built dist on this branch (no test mocks), comparing the old main-branch lookup (argv.model || settings.model?.name) against the PR lookup (argv.model > OPENAI_MODEL > QWEN_MODEL > settings.model.name).

Setup: settings.model.name=settings-model, and modelProviders.openai has 4 entries each with a distinct baseUrl / envKey.

Case main (old) PR (new) Expected provider
argv.model=cli-model ✅ cli-model ✅ cli-model cli-model
only OPENAI_MODEL=env-model ❌ wrongly applies settings-model baseUrl/envKey ✅ env-model env-model
only QWEN_MODEL=qwen-env-model ❌ wrongly applies settings-model baseUrl/envKey ✅ qwen-env-model qwen-env-model
no env, falls back to settings ✅ settings-model ✅ settings-model settings-model

Raw output

Case: argv.model=cli-model   (expected provider: cli-model)
  main  [OK ]  model=cli-model        baseUrl=https://cli.example.com/v1       provider=cli-model
  PR    [OK ]  model=cli-model        baseUrl=https://cli.example.com/v1       provider=cli-model

Case: only OPENAI_MODEL=env-model   (expected provider: env-model)
  main  [BAD]  model=settings-model   baseUrl=https://settings.example.com/v1  provider=settings-model
  PR    [OK ]  model=env-model        baseUrl=https://env.example.com/v1       provider=env-model

Case: only QWEN_MODEL   (expected provider: qwen-env-model)
  main  [BAD]  model=settings-model   baseUrl=https://settings.example.com/v1  provider=settings-model
  PR    [OK ]  model=qwen-env-model   baseUrl=https://qwen.example.com/v1      provider=qwen-env-model

Case: no env, settings fallback   (expected provider: settings-model)
  main  [OK ]  model=settings-model   baseUrl=https://settings.example.com/v1  provider=settings-model
  PR    [OK ]  model=settings-model   baseUrl=https://settings.example.com/v1  provider=settings-model

Key repro finding

When the user sets only OPENAI_MODEL=foo while settings.model.name points at a different id, the old CLI lookup used the settings name against modelProviders, so the wrong provider's baseUrl / envKey got merged into the final config — while the core resolver's final model id was still foo. This caused a drift between the resolved model name and the provider-specific credentials/endpoint. The PR aligns the CLI-layer lookup with the core resolver's precedence, fixing cases 2 and 3 while leaving cases 1 and 4 unchanged.

LGTM ✅

@wenshao wenshao merged commit 007a109 into QwenLM:main Apr 24, 2026
13 checks passed
@B-A-M-N

B-A-M-N commented Apr 26, 2026

Copy link
Copy Markdown
Contributor Author

I recently fixed provider id resolution drift in #3567.

A follow-up problem I noticed is that when provider resolution fails or falls back, users often cannot tell whether the issue is:

  • provider id mismatch
  • missing env key
  • missing API key
  • model id/provider mismatch
  • default fallback behavior

I'm going to take a small scoped pass at adding debug diagnostics around provider resolution without changing runtime behavior.

@wenshao

wenshao commented Apr 26, 2026

Copy link
Copy Markdown
Collaborator

Thanks @B-A-M-N — and great instinct on the follow-up. The "silent fallback with no signal as to which of N reasons fired" pattern is exactly the kind of thing that eats up support time, and a scoped diagnostics-only pass is the right shape for it (no behavior change, easy to review, easy to revert).

Looking forward to the follow-up PR. Please tag me when it's up and I'll prioritize the review.

@tanzhenxin

Copy link
Copy Markdown
Collaborator

Hi @B-A-M-N, after tracing this end-to-end, we're going to revert this PR. Wanted to leave the reasoning here for the record.

Scenario:

  1. User has OPENAI_MODEL=qwen3-coder-plus exported in their shell rc (set months ago, forgotten).
  2. User runs /model qwen-coder-turbo in the CLI to switch — this writes settings.model.name, and the user has a corresponding entry in settings.modelProviders so the choice persists across sessions.
  3. User restarts.

Pre-PR, the CLI looked up the provider entry by settings.model.name, found it, and the matched modelProvider.id won via the core resolver's modelProvider > cli > env > settings priority. The /model choice was respected.

Post-PR, the lookup uses OPENAI_MODEL first, doesn't match any entry in modelProviders, returns undefined, and env wins via fallback. The /model choice is silently overridden — no warning, no surface.

The contract this breaks: pre-PR, an explicit settings.modelProviders config carried the meaning "I've configured these provider entries deliberately; don't let a stale OPENAI_MODEL swap which one is selected." If no providers were configured, env still drove the model — so OPENAI_MODEL wasn't being ignored, it was yielding to a more explicit user signal. This PR removes that boundary with no replacement (no warning, no opt-out).

Worth noting: model is the one field users actively switch via a CLI command (/model), which makes drift between env and settings most painful here — different from OPENAI_API_KEY / OPENAI_BASE_URL, where env-as-override is fine.

Revert: packages/cli/src/utils/modelConfigUtils.ts:103-109 goes back to const requestedModel = argv.model || settings.model?.name;, and the env-precedence tests added here are dropped.

If you have a specific user-reported bug this was addressing, please share — happy to take another look at the underlying issue and find a fix that doesn't regress the /model flow.

tanzhenxin added a commit that referenced this pull request Apr 26, 2026
…tion (#3567)" (#3633)

This reverts commit 007a109.

The change made `OPENAI_MODEL` outrank `settings.model.name` when looking
up the active entry in `settings.modelProviders`. Combined with the core
resolver's `modelProvider > cli > env > settings` priority, this caused
a regression: a `/model` selection (which writes `settings.model.name`)
was silently overridden whenever `OPENAI_MODEL` was set in the user's
shell, with no warning surfaced.

Restoring the previous behavior — looking up the provider entry by
`argv.model || settings.model?.name` — preserves the implicit contract
that an explicit `modelProviders` config takes precedence over stale
shell defaults. Users without a `modelProviders` config are unaffected:
env vars still drive model selection through the core resolver.

See discussion on #3567.
B-A-M-N added a commit to B-A-M-N/qwen-code that referenced this pull request Apr 27, 2026
…ection

Fixes model precedence regression from QwenLM#3567 (reverted in QwenLM#3633):

- argv.model > settings.model.name > OPENAI_MODEL > QWEN_MODEL
- settings.model.name wins over OPENAI_MODEL when it matches a provider
- OPENAI_MODEL only used as fallback when no explicit model is configured
- Added tests proving both paths: settings wins, and OPENAI_MODEL fallback works

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
wenshao pushed a commit that referenced this pull request Apr 30, 2026
…3645)

* fix(cli): correct OPENAI_MODEL precedence without breaking /model selection

Fixes model precedence regression from #3567 (reverted in #3633):

- argv.model > settings.model.name > OPENAI_MODEL > QWEN_MODEL
- settings.model.name wins over OPENAI_MODEL when it matches a provider
- OPENAI_MODEL only used as fallback when no explicit model is configured
- Added tests proving both paths: settings wins, and OPENAI_MODEL fallback works

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(cli): separate model selection from provider lookup to prevent env override

The previous implementation iterated through candidates [argv.model, settings.model.name,
OPENAI_MODEL, QWEN_MODEL] and picked the first matching provider. This caused
settings.model.name to be overridden by OPENAI_MODEL when the former didn't
match any provider.

Fix by:
1. First resolve target model using strict precedence
2. Only look up provider for that specific model
3. Filter OPENAI_MODEL/QWEN_MODEL from env when model was resolved
   from settings or argv, preventing the core resolver from picking
   up these env vars

Also fixes Edge Case 5 test (was testing buggy behavior) and adds
integration test verifying settings.model.name wins over OPENAI_MODEL.

* fix(types): remove unused type annotation from mock

The mockImplementation used ModelConfigSourcesInput type which
wasn't properly resolved. Remove the type annotation
to fix TypeScript and ESLint errors.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* trigger CI

* chore: trigger clean CI build

* fix(test): make mock compatible with CI type checking

The mockImplementation was causing TS2345 in CI because
the return type didn't match ModelConfigResolutionResult.
Fixed by using optional chaining and removing type annotations
that caused CI build failures.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(test): make mock compatible with CI type checking

The mock of resolveModelConfig returned sources as { model: string }
instead of { model: ConfigSource }, causing a type error in CI only
(where strictBuild is enabled). Use proper ConfigSource objects.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(test): update mock to match reviewer suggestion

Use proper ConfigSource objects with path/envKey details as suggested
in the review, matching what resolveModelConfig actually returns.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* test(cli): add regression tests for model precedence

Adds [Regression] tests that guard against the original bug where
OPENAI_MODEL incorrectly overrode settings.model.name. Tests cover:
- settings.model.name precedence over OPENAI_MODEL
- OPENAI_MODEL used when settings.model.name not set
- argv.model overriding both settings and env
- QWEN_MODEL as fallback when OPENAI_MODEL not set
- Non-OpenAI auth ignoring OPENAI_MODEL

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(cli): address reviewer feedback on model precedence PR

- Add missing assertion in Non-OpenAI auth test to verify OPENAI_MODEL
  is filtered from env passed to resolveModelConfig
- Clean up modelProvider lookup comment to clarify the old bug is fixed

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(cli): address review feedback on model precedence PR

- Sanitize OPENAI_MODEL/QWEN_MODEL in beforeEach to prevent flaky tests
- Remove stray "// trigger rebuild" comment
- Add AUTH_ENV_MODEL_VARS mapping for all auth types (not just OpenAI)
- Fix filteredEnv logic to strip ALL model env vars when model not from env
- Use sourceEnvVar tracking to only keep the env var that was actually used

Fixes the blocking test failure when OPENAI_MODEL is set in shell env.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(cli): fix test assertion for filtered env, add source-based filtering comments

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Co-authored-by: Mistral Vibe <vibe@mistral.ai>
xaelistic pushed a commit to xaelistic/qwen-code that referenced this pull request Jun 7, 2026
…enLM#3567)

* fix(cli): respect OPENAI_MODEL precedence in CLI model resolution

* test(cli): cover env-driven model precedence for OpenAI-compatible auth

* fix(cli): scope model env precedence by auth type

* test(cli): cover QWEN_MODEL fallback precedence
xaelistic pushed a commit to xaelistic/qwen-code that referenced this pull request Jun 7, 2026
…tion (QwenLM#3567)" (QwenLM#3633)

This reverts commit 4420229.

The change made `OPENAI_MODEL` outrank `settings.model.name` when looking
up the active entry in `settings.modelProviders`. Combined with the core
resolver's `modelProvider > cli > env > settings` priority, this caused
a regression: a `/model` selection (which writes `settings.model.name`)
was silently overridden whenever `OPENAI_MODEL` was set in the user's
shell, with no warning surfaced.

Restoring the previous behavior — looking up the provider entry by
`argv.model || settings.model?.name` — preserves the implicit contract
that an explicit `modelProviders` config takes precedence over stale
shell defaults. Users without a `modelProviders` config are unaffected:
env vars still drive model selection through the core resolver.

See discussion on QwenLM#3567.
xaelistic pushed a commit to xaelistic/qwen-code that referenced this pull request Jun 7, 2026
…wenLM#3645)

* fix(cli): correct OPENAI_MODEL precedence without breaking /model selection

Fixes model precedence regression from QwenLM#3567 (reverted in QwenLM#3633):

- argv.model > settings.model.name > OPENAI_MODEL > QWEN_MODEL
- settings.model.name wins over OPENAI_MODEL when it matches a provider
- OPENAI_MODEL only used as fallback when no explicit model is configured
- Added tests proving both paths: settings wins, and OPENAI_MODEL fallback works

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(cli): separate model selection from provider lookup to prevent env override

The previous implementation iterated through candidates [argv.model, settings.model.name,
OPENAI_MODEL, QWEN_MODEL] and picked the first matching provider. This caused
settings.model.name to be overridden by OPENAI_MODEL when the former didn't
match any provider.

Fix by:
1. First resolve target model using strict precedence
2. Only look up provider for that specific model
3. Filter OPENAI_MODEL/QWEN_MODEL from env when model was resolved
   from settings or argv, preventing the core resolver from picking
   up these env vars

Also fixes Edge Case 5 test (was testing buggy behavior) and adds
integration test verifying settings.model.name wins over OPENAI_MODEL.

* fix(types): remove unused type annotation from mock

The mockImplementation used ModelConfigSourcesInput type which
wasn't properly resolved. Remove the type annotation
to fix TypeScript and ESLint errors.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* trigger CI

* chore: trigger clean CI build

* fix(test): make mock compatible with CI type checking

The mockImplementation was causing TS2345 in CI because
the return type didn't match ModelConfigResolutionResult.
Fixed by using optional chaining and removing type annotations
that caused CI build failures.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(test): make mock compatible with CI type checking

The mock of resolveModelConfig returned sources as { model: string }
instead of { model: ConfigSource }, causing a type error in CI only
(where strictBuild is enabled). Use proper ConfigSource objects.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(test): update mock to match reviewer suggestion

Use proper ConfigSource objects with path/envKey details as suggested
in the review, matching what resolveModelConfig actually returns.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* test(cli): add regression tests for model precedence

Adds [Regression] tests that guard against the original bug where
OPENAI_MODEL incorrectly overrode settings.model.name. Tests cover:
- settings.model.name precedence over OPENAI_MODEL
- OPENAI_MODEL used when settings.model.name not set
- argv.model overriding both settings and env
- QWEN_MODEL as fallback when OPENAI_MODEL not set
- Non-OpenAI auth ignoring OPENAI_MODEL

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(cli): address reviewer feedback on model precedence PR

- Add missing assertion in Non-OpenAI auth test to verify OPENAI_MODEL
  is filtered from env passed to resolveModelConfig
- Clean up modelProvider lookup comment to clarify the old bug is fixed

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(cli): address review feedback on model precedence PR

- Sanitize OPENAI_MODEL/QWEN_MODEL in beforeEach to prevent flaky tests
- Remove stray "// trigger rebuild" comment
- Add AUTH_ENV_MODEL_VARS mapping for all auth types (not just OpenAI)
- Fix filteredEnv logic to strip ALL model env vars when model not from env
- Use sourceEnvVar tracking to only keep the env var that was actually used

Fixes the blocking test failure when OPENAI_MODEL is set in shell env.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(cli): fix test assertion for filtered env, add source-based filtering comments

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Co-authored-by: Mistral Vibe <vibe@mistral.ai>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants