From 8fb16561ed5f031c211c858efcf35fcd542a24f0 Mon Sep 17 00:00:00 2001 From: gitdevine <210788251+gitdevine@users.noreply.github.com> Date: Thu, 18 Jun 2026 20:13:30 +0200 Subject: [PATCH] fix(gemini-vertex): doctor validates the Vertex credential before launch The Vertex generation-readiness check unconditionally passed ("relying on ambient ADC"), so a fresh env with a project but no resolvable ADC passed the doctor that provider-launch runs as its preflight, then `node bin/openclaude` failed startup validation. Reuse the runtime resolveGeminiCredential() (mapping GEMINI_VERTEX_AUTH_MODE -> GEMINI_AUTH_MODE and clearing API keys, exactly like the native client) and fail when the credential cannot be resolved, so the preflight matches the provider that will actually run. Also clarify the GEMINI_VERTEX_AUTH line that ambient ADC is validated by the readiness check. Tests: readiness passes when a credential resolves and fails when none does. Co-Authored-By: Claude Fable 5 --- scripts/system-check.test.ts | 28 ++++++++++++++++++++++++++++ scripts/system-check.ts | 35 +++++++++++++++++++++++++++++++---- 2 files changed, 59 insertions(+), 4 deletions(-) diff --git a/scripts/system-check.test.ts b/scripts/system-check.test.ts index 6ea7742ac..fec3817b6 100644 --- a/scripts/system-check.test.ts +++ b/scripts/system-check.test.ts @@ -495,4 +495,32 @@ describe('doctor recognises Gemini Vertex', () => { expect(summary.GEMINI_VERTEX_PROJECT).toBe('my-proj') expect(summary.GEMINI_VERTEX_CREDENTIAL_SET).toBe(true) }) + + test('generation readiness passes when a Vertex credential resolves', async () => { + const { __test } = await import('./system-check.ts') + for (const k of FLAGS) delete process.env[k] + process.env.CLAUDE_CODE_USE_GEMINI_VERTEX = '1' + process.env.GEMINI_VERTEX_PROJECT = 'my-proj' + process.env.GEMINI_VERTEX_AUTH_MODE = 'access-token' + process.env.GEMINI_ACCESS_TOKEN = 'tok' + + const result = await __test.checkProviderGenerationReadiness() + expect(result.ok).toBe(true) + expect(result.detail).toContain('resolved') + }) + + test('generation readiness fails when no Vertex credential resolves (preflight matches startup)', async () => { + const { __test } = await import('./system-check.ts') + for (const k of FLAGS) delete process.env[k] + process.env.CLAUDE_CODE_USE_GEMINI_VERTEX = '1' + process.env.GEMINI_VERTEX_PROJECT = 'my-proj' + // Same fail branch the runtime resolver takes when ambient ADC cannot be + // found: an explicit auth mode with no usable credential must NOT report a + // false green, or the launcher preflight would pass and startup would fail. + process.env.GEMINI_VERTEX_AUTH_MODE = 'access-token' + + const result = await __test.checkProviderGenerationReadiness() + expect(result.ok).toBe(false) + expect(result.detail).toContain('could not be resolved') + }) }) diff --git a/scripts/system-check.ts b/scripts/system-check.ts index 740560f4e..2c3c8554e 100644 --- a/scripts/system-check.ts +++ b/scripts/system-check.ts @@ -26,7 +26,10 @@ import { redactSecretSubstringsForDisplay, type SecretValueSource, } from '../src/utils/providerSecrets.js' -import { getGeminiVertexProjectId } from '../src/utils/geminiAuth.js' +import { + getGeminiVertexProjectId, + resolveGeminiCredential, +} from '../src/utils/geminiAuth.js' import { redactUrlForDisplay } from '../src/utils/urlRedaction.js' import { MIN_NODE_ENGINE_RANGE, @@ -430,7 +433,7 @@ function checkGeminiVertexEnv(): CheckResult[] { results.push( adcFile ? pass('GEMINI_VERTEX_AUTH', 'ADC via GOOGLE_APPLICATION_CREDENTIALS.') - : pass('GEMINI_VERTEX_AUTH', 'ADC mode — relying on ambient Google Application Default Credentials.'), + : pass('GEMINI_VERTEX_AUTH', 'ADC mode — ambient Google ADC is validated by the generation-readiness check below.'), ) } @@ -729,9 +732,29 @@ async function checkProviderGenerationReadiness(): Promise { const useMistral = isTruthy(process.env.CLAUDE_CODE_USE_MISTRAL) if (useGeminiVertex) { + // Reuse the runtime credential resolver so this preflight matches what + // startup/runtime will accept. Otherwise "relying on ambient ADC" is a + // false green: a fresh env with a project but no resolvable ADC passes the + // doctor, then `node bin/openclaude` fails startup validation. Map the + // Vertex auth mode to GEMINI_AUTH_MODE and clear API keys, exactly like the + // native client does, then fail when nothing actually resolves. + const credential = await resolveGeminiCredential({ + ...process.env, + GEMINI_AUTH_MODE: + process.env.GEMINI_VERTEX_AUTH_MODE ?? + (process.env.GEMINI_ACCESS_TOKEN ? 'access-token' : 'adc'), + GEMINI_API_KEY: undefined, + GOOGLE_API_KEY: undefined, + }) + if (credential.kind === 'none') { + return fail( + 'Provider generation readiness', + 'Gemini Vertex authentication could not be resolved. access-token mode needs GEMINI_ACCESS_TOKEN; ADC mode needs GOOGLE_APPLICATION_CREDENTIALS or ambient Google ADC (e.g. `gcloud auth application-default login`).', + ) + } return pass( 'Provider generation readiness', - 'Skipped for Gemini Vertex (native generateContent flow; env/credential checks above cover setup).', + `Gemini Vertex credential resolved (${credential.kind}).`, ) } @@ -1029,4 +1052,8 @@ if (import.meta.main) { } // Test-only surface for the provider dispatch. -export const __test = { checkOpenAIEnv, serializeSafeEnvSummary } +export const __test = { + checkOpenAIEnv, + serializeSafeEnvSummary, + checkProviderGenerationReadiness, +}