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, +}