From 20f627fb67955f76897f4484f5fd5b2b2a6c063e Mon Sep 17 00:00:00 2001 From: Jeremy Date: Sun, 12 Apr 2026 19:16:16 -0500 Subject: [PATCH 1/2] fix(doctor): skip key check for CLI-authenticated providers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Providers like claude-code, openai-codex, google-gemini-cli use external CLI auth — they don't need API keys. The doctor was incorrectly reporting "claude-code key missing" for subscription users. --- .../extensions/gsd/doctor-providers.ts | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/resources/extensions/gsd/doctor-providers.ts b/src/resources/extensions/gsd/doctor-providers.ts index e0f35341b..06242fc81 100644 --- a/src/resources/extensions/gsd/doctor-providers.ts +++ b/src/resources/extensions/gsd/doctor-providers.ts @@ -185,11 +185,35 @@ const PROVIDER_ROUTES: Record = { 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" From e9e28501652b4d8a1f8cef181936ccf62a6798ef Mon Sep 17 00:00:00 2001 From: Jeremy Date: Sun, 12 Apr 2026 19:24:29 -0500 Subject: [PATCH 2/2] test(doctor): add regression test for claude-code CLI auth provider Verifies that claude-code provider is reported as ok without any API key, since it uses external CLI authentication. --- .../gsd/tests/doctor-providers.test.ts | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/src/resources/extensions/gsd/tests/doctor-providers.test.ts b/src/resources/extensions/gsd/tests/doctor-providers.test.ts index 8df31fc10..3fee92d75 100644 --- a/src/resources/extensions/gsd/tests/doctor-providers.test.ts +++ b/src/resources/extensions/gsd/tests/doctor-providers.test.ts @@ -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");