fix(providers): surface CLI logins as their own providers, not API-provider fallbacks#3059
Merged
Conversation
The CLI-provider fallback detectors for Gemini, Claude Code, and Qwen Code were matching non-credential files, auto-marking providers as "configured" for anyone who had merely installed the CLI. - gemini-cli: was checking `settings.json` (a preferences file created on first launch) and `.credentials.json` (wrong filename). Now checks `oauth_creds.json` (the actual Google Gemini CLI OAuth token) plus defensive fallbacks. - claude-code: was checking `settings.json` with a comment claiming it covered keychain-auth users. In practice settings.json is written without login, and the primary `detect()` path already probes the binary at PATH + common install locations, so keychain users are still covered. Dropped the false-positive signal. - qwen-code: Qwen Code forks Gemini CLI, so added `oauth_creds.json` alongside the existing fallbacks to match logged-in users. Each detector now delegates to a `*_credentials_in_dir(&Path)` helper so the logic is directly unit-testable without HOME manipulation.
Previously a CLI login (Claude Code, Codex CLI, Gemini CLI, Qwen Code) would silently mark its corresponding API provider (anthropic, openai, gemini, qwen) as Configured/AutoDetected, causing the dashboard to show each credential twice — once under the CLI provider and once under the API provider — and letting `provider = "openai"` silently run on a Codex-extracted token without the user ever setting `OPENAI_API_KEY`. New rule: CLI login surfaces only as its own CLI provider; API provider lights up only when the user sets its own API key. Changes: - `model_catalog.rs::detect_auth`: drop `has_cli_fallback` and the `read_codex_credential()` clause from `has_key_fallback`. API providers now only look at their own (and registered alias) env vars. Renamed `has_key_fallback` → `has_key_alias` to reflect that it's for documented env-var aliases (GOOGLE_API_KEY for Gemini), not CLI-credential borrowing. Removed the unused `read_codex_credential()` helper. - `drivers/mod.rs`: runtime path no longer falls back to the Codex auth.json when `OPENAI_API_KEY` is unset — behavior now matches detection. Removed `read_codex_credential()` and its callers. - `providers.rs::test_provider`: removed the dead `ConfiguredCli` branch; no API provider produces that status anymore. `AuthStatus::ConfiguredCli` variant itself is kept (public enum) but is no longer produced. Existing matches treat it as available, so any stale serialised value round-trips harmlessly. Added regression tests in `model_catalog.rs`: - `detect_auth_does_not_promote_api_providers_from_cli_login` - `google_api_key_alias_still_recognised_for_gemini`
The two regression tests each declared their own `static ENV_LOCK: Mutex<()>`, which meant the locks were disjoint and parallel execution could race on `GEMINI_API_KEY` / `GOOGLE_API_KEY`. Promote the mutex to module scope so both tests contend on the same lock.
3 tasks
houko
added a commit
that referenced
this pull request
Apr 24, 2026
…3061) Follow-up to #3059, which stopped silently promoting CLI logins into API-provider auth status. That fix surfaced a UX gap: users with only a CLI login (e.g. Codex CLI signed in, no `OPENAI_API_KEY` set) and `provider = "auto"` (or a default config pointing at an API provider they never configured) now hit: WARN Primary LLM driver init failed — trying auto-detect provider=openai error=Missing API key WARN No LLM drivers available — agents will return errors The daemon's `detect_available_provider()` only scanned API-key env vars; CLI providers were skipped by the `!p.key_required` guard, so there was no way for auto-detect to recover. Changes: - `drivers/mod.rs::detect_available_provider`: add Phase 3 that scans CLI-backed providers (`claude-code`, `codex-cli`, `gemini-cli`, `qwen-code`) after all API-key providers. Returns an empty `api_key_env` to signal "no env lookup needed — driver handles its own auth". API-key providers still win when both are available so users with an API key get the direct-API path. - `kernel/mod.rs` (two call sites) and `cli/main.rs`: render an empty env_var as "CLI login" in log messages / user-facing strings so operators can tell at a glance that the daemon chose a CLI subprocess. Avoids trailing "()" / "from —" artefacts. With this change, a machine with only `~/.codex/auth.json` boots with `provider = "codex-cli"` instead of falling into the "no drivers available" state.
This was referenced Apr 25, 2026
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Two-commit fix for the provider-detection mess where CLI installs and CLI logins were being treated as API-provider credentials, causing false-positive detection and duplicate rows in the provider list.
Commit 1 ·
fix(llm-drivers): stop treating CLI config files as auth credentialsBug:
settings.json/ wrong credential filenames were matched as proof of authentication.Reported trigger: fresh install,
GEMINI_API_KEY/GOOGLE_API_KEYunset, vault empty — Gemini still showed as auto-detected because~/.gemini/settings.json(a preferences file created on first launch) was being checked.settings.json(preference file, always exists) and.credentials.json(wrong filename). Now checksoauth_creds.json(Google Gemini CLI's actual OAuth token) +credentials.json/.credentials.jsonfallbacks.settings.jsonwith a comment claiming it covered keychain-auth users.settings.jsonis written before login, and the primarydetect()path already probes theclaudebinary at PATH +/opt/homebrew/bin+/usr/local/bin+/usr/bin, so keychain users stay covered without the false-positive signal.oauth_creds.jsonalongside existing fallbacks to match logged-in users that were previously missed.~/.codex/auth.json+ validates token expiry).Each detector now delegates to a
*_credentials_in_dir(&Path)helper so tests drive it against a tempdir withoutHOMEmanipulation.Commit 2 ·
fix(providers): stop promoting CLI logins into API provider authBug: a CLI login silently promoted its corresponding API provider to Configured/AutoDetected:
claude-codelogin →anthropicmarked ConfiguredClicodex-clilogin →openaimarked AutoDetected (viaread_codex_credential()extracting the token from~/.codex/auth.json)gemini-clilogin →geminimarked ConfiguredCliqwen-codelogin →qwenmarked ConfiguredCliDashboard then showed each credential twice (once under the CLI provider, once under the API provider), and
provider = "openai"could run silently on a Codex-extracted token withoutOPENAI_API_KEYever being set.New contract: CLI login surfaces only as its own CLI provider entry; API provider lights up only when the user sets its own API key.
model_catalog.rs::detect_auth: droppedhas_cli_fallbackand theread_codex_credential()clause fromhas_key_fallback. Renamed tohas_key_alias— only Gemini'sGOOGLE_API_KEYalias remains (Google AI Studio officially documents it as equivalent toGEMINI_API_KEY). Removed the now-unusedpub fn read_codex_credential()helper.drivers/mod.rs: runtime driver factory andresolve_provider_api_key()no longer fall back to Codex auth.json whenOPENAI_API_KEYis empty — runtime matches detection.providers.rs::test_provider: removed the deadConfiguredClibranch; no API provider produces that status anymore.AuthStatus::ConfiguredClivariant is retained (public enum) but unproduced; existing matches treat it as available so any stale serialised value round-trips harmlessly.Behavior change — release-note worthy
Agents previously configured with
provider = "openai"and noOPENAI_API_KEYwere silently running on a Codex CLI token. Those will now returnMissingApiKey. Migration: either setOPENAI_API_KEY, or switch the agent toprovider = "codex-cli"(uses the subprocess path).Test plan
cargo test -p librefang-llm-drivers --lib— 342 passed (10 new credential-detection tests)cargo test -p librefang-runtime --lib— 2 new regression tests inmodel_catalog::testspasscargo clippy -p librefang-llm-drivers --all-targets -- -D warnings— clean~/.gemini/settings.json, confirm Gemini no longer appears as Configured in the provider list~/.codex/auth.json, confirmopenaiappears as Missing (butcodex-clistill appears as Configured)OPENAI_API_KEYset, confirmopenaiappears as ConfiguredFollow-ups (not in this PR)
dashboard/src/pages/ProvidersPage.tsx/ChatPage.tsxstill branch on"configured_cli"— dead code now, safe to clean up.claude_credentials_exist/qwen_credentials_exist/gemini_cli_credentials_exist— three spellings for the same concept (pre-existing).