From 503140d8d5c8de8e598653edf245d8babd8e73a4 Mon Sep 17 00:00:00 2001 From: Hunter Bown Date: Sun, 10 May 2026 00:29:28 -0500 Subject: [PATCH] fix(config): warn when root base_url is set with non-DeepSeek provider (#1308) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Common footgun: users set api_provider = \"ollama\" (or vllm / openrouter / etc.) at the top of config.toml and add a top-level base_url = \"http://my-server\" alongside it. The root base_url field is only read for DeepSeek/DeepseekCN (and a back-compat sniff for NvidiaNim) — for every other provider it's silently ignored, and the user can't figure out why their override doesn't apply. Add a one-line tracing::warn at config load time pointing the user at the matching `[providers.]` table or the corresponding `*_BASE_URL` env var. Skipped if the per-provider table already has its own `base_url` (which would win anyway). No behavior change to URL resolution. Co-Authored-By: Claude Opus 4.7 (1M context) --- CHANGELOG.md | 5 ++++ crates/tui/src/config.rs | 51 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 618fd9ef..10cdf347 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,11 @@ published. ### Fixed +- **Hint when root `base_url` is set with a non-DeepSeek provider + (#1308)** — config load now logs a warning telling the user to + move the URL under the matching `[providers.]` table or use + the `*_BASE_URL` env var. Closes the silent-ignore footgun for + Ollama / vLLM / OpenAI-compatible setups. - **Insecure base-URL error message is more discoverable (#1303)** — the rejection now spells out which env var to set (with underscores visible), notes that loopback hosts are auto-allowed, and shows a diff --git a/crates/tui/src/config.rs b/crates/tui/src/config.rs index 5048f7cb..b4cfb5f5 100644 --- a/crates/tui/src/config.rs +++ b/crates/tui/src/config.rs @@ -1078,9 +1078,60 @@ impl Config { apply_requirements(&mut config)?; normalize_model_config(&mut config); config.validate()?; + config.warn_on_misplaced_root_base_url(); Ok(config) } + /// Surface a one-line warning when the user has set the legacy root + /// `base_url` field but their active provider is not DeepSeek (the only + /// provider that actually reads that field, plus an NvidiaNim back-compat + /// sniff). Common confusion: users add `base_url = "..."` at the top of + /// `~/.deepseek/config.toml` for ollama / vllm / openai-compat servers + /// and wonder why it's silently ignored (#1308). + fn warn_on_misplaced_root_base_url(&self) { + let Some(root_base) = self.base_url.as_deref().map(str::trim) else { + return; + }; + if root_base.is_empty() { + return; + } + let provider = self.api_provider(); + if matches!(provider, ApiProvider::Deepseek | ApiProvider::DeepseekCN) { + return; + } + if matches!(provider, ApiProvider::NvidiaNim) + && root_base.contains("integrate.api.nvidia.com") + { + return; + } + // Only warn if the per-provider table doesn't have an explicit + // `base_url`, because if it does, the per-provider one wins and the + // root field is just dead config — no behavior surprise. + let has_provider_base = self + .provider_config_for(provider) + .and_then(|p| p.base_url.as_deref().map(str::trim)) + .is_some_and(|s| !s.is_empty()); + if has_provider_base { + return; + } + let table = match provider { + ApiProvider::Openai => "providers.openai", + ApiProvider::Openrouter => "providers.openrouter", + ApiProvider::Novita => "providers.novita", + ApiProvider::Fireworks => "providers.fireworks", + ApiProvider::Sglang => "providers.sglang", + ApiProvider::Vllm => "providers.vllm", + ApiProvider::Ollama => "providers.ollama", + ApiProvider::NvidiaNim => "providers.nvidia_nim", + ApiProvider::Deepseek | ApiProvider::DeepseekCN => return, + }; + tracing::warn!( + "Top-level `base_url = \"{root_base}\"` is ignored for the {provider:?} provider. \ + Move it under `[{table}]` (e.g. `[{table}]\\nbase_url = \"...\"`) \ + or set the corresponding `*_BASE_URL` env var. (#1308)" + ); + } + /// Validate that critical config fields are present. pub fn validate(&self) -> Result<()> { if let Some(provider) = self.provider.as_deref()