Merge pull request #4081 from jeremymcs/fix/doctor-cli-auth-providers

fix(doctor): skip key check for CLI-authenticated providers
This commit is contained in:
Jeremy McSpadden 2026-04-12 19:37:10 -05:00 committed by GitHub
commit 99a68c05b7
2 changed files with 60 additions and 0 deletions

View file

@ -185,11 +185,35 @@ const PROVIDER_ROUTES: Record<string, string[]> = {
google: ["google-gemini-cli"],
};
/**
* Providers that use external CLI authentication (not API keys).
* These are always considered "ok" the host CLI handles auth.
*/
const CLI_AUTH_PROVIDERS = new Set([
"claude-code",
"openai-codex",
"google-gemini-cli",
"google-antigravity",
]);
function checkLlmProviders(): ProviderCheckResult[] {
const required = collectConfiguredModelProviders();
const results: ProviderCheckResult[] = [];
for (const providerId of required) {
// CLI-authenticated providers don't need API keys — skip key check
if (CLI_AUTH_PROVIDERS.has(providerId)) {
const info = PROVIDER_REGISTRY.find(p => p.id === providerId);
results.push({
name: providerId,
label: info?.label ?? providerId,
category: "llm",
status: "ok",
message: `${info?.label ?? providerId} — CLI auth (no key needed)`,
required: true,
});
continue;
}
const info = PROVIDER_REGISTRY.find(p => p.id === providerId);
const label = providerId === "anthropic-vertex"
? "Anthropic Vertex"

View file

@ -574,6 +574,42 @@ test("runProviderChecks reports ok for OpenAI via openai-codex auth.json (#2922)
rmSync(tmpHome, { recursive: true, force: true });
});
test("runProviderChecks reports ok for claude-code without any API key", () => {
const repo = realpathSync(mkdtempSync(join(tmpdir(), "gsd-providers-cc-repo-")));
mkdirSync(join(repo, ".gsd"), { recursive: true });
writeFileSync(
join(repo, ".gsd", "PREFERENCES.md"),
[
"---",
"models:",
" execution:",
" model: claude-sonnet-4-6",
" provider: claude-code",
"---",
"",
].join("\n"),
);
const tmpHome = realpathSync(mkdtempSync(join(tmpdir(), "gsd-providers-cc-home-")));
withEnv({
HOME: tmpHome,
ANTHROPIC_API_KEY: undefined,
ANTHROPIC_OAUTH_TOKEN: undefined,
}, () => {
withCwd(repo, () => {
const results = runProviderChecks();
const cc = results.find(r => r.name === "claude-code");
assert.ok(cc, "claude-code result should exist");
assert.equal(cc!.status, "ok", "claude-code uses CLI auth — must be ok without API keys");
assert.ok(cc!.message.includes("CLI auth"), "should indicate CLI auth");
});
});
rmSync(repo, { recursive: true, force: true });
rmSync(tmpHome, { recursive: true, force: true });
});
test("PROVIDER_ROUTES includes google-gemini-cli as route for google (#2922)", async () => {
const { readFileSync: readFS } = await import("node:fs");
const { dirname: dirn, join: joinPath } = await import("node:path");