summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTommaso Sciortino <[email protected]>2025-06-23 18:37:41 -0700
committerGitHub <[email protected]>2025-06-24 01:37:41 +0000
commit0abd2a644e947b7794dd68615d5d3d1553b0b5fd (patch)
tree66c694836649c2b9cec97acdab6a5ab429329618
parent104f23da90f506c75ac9ec44cbe63dacf0f04dcf (diff)
Improve Auth error messaging (#1358)
-rw-r--r--docs/cli/authentication.md2
-rw-r--r--packages/cli/src/ui/components/AuthDialog.tsx2
-rw-r--r--packages/cli/src/ui/hooks/useAuthCommand.ts2
-rw-r--r--packages/cli/src/ui/hooks/useGeminiStream.ts4
-rw-r--r--packages/core/src/code_assist/errors.ts13
-rw-r--r--packages/core/src/core/client.ts3
-rw-r--r--packages/core/src/core/turn.ts7
-rw-r--r--packages/core/src/index.ts1
-rw-r--r--packages/core/src/utils/errors.ts52
9 files changed, 56 insertions, 30 deletions
diff --git a/docs/cli/authentication.md b/docs/cli/authentication.md
index f1a8ba58..b74e2b60 100644
--- a/docs/cli/authentication.md
+++ b/docs/cli/authentication.md
@@ -22,7 +22,7 @@ The Gemini CLI requires you to authenticate with Google's AI services. On initia
source ~/.bashrc
```
-3. **Login with Google Work**
+3. **Login with Google Workspace**
- Use this option to log in with the **Google Workspace Accounts**. This is a paid service for businesses and organizations that provides a suite of productivity tools, including a custom email domain (e.g. [email protected]), enhanced security features, and administrative controls. These accounts are often managed by an employer or school.
- Google Workspace Account must configure a Google Cloud Project Id to use. You can temporarily set the environment variable in your current shell session using the following command:
diff --git a/packages/cli/src/ui/components/AuthDialog.tsx b/packages/cli/src/ui/components/AuthDialog.tsx
index c0e5d4f8..ba19df70 100644
--- a/packages/cli/src/ui/components/AuthDialog.tsx
+++ b/packages/cli/src/ui/components/AuthDialog.tsx
@@ -35,7 +35,7 @@ export function AuthDialog({
},
{ label: 'Gemini API Key', value: AuthType.USE_GEMINI },
{
- label: 'Login with Google Work',
+ label: 'Login with Google Workspace',
value: AuthType.LOGIN_WITH_GOOGLE_ENTERPRISE,
},
{ label: 'Vertex AI', value: AuthType.USE_VERTEX_AI },
diff --git a/packages/cli/src/ui/hooks/useAuthCommand.ts b/packages/cli/src/ui/hooks/useAuthCommand.ts
index 5cc67a07..fe890706 100644
--- a/packages/cli/src/ui/hooks/useAuthCommand.ts
+++ b/packages/cli/src/ui/hooks/useAuthCommand.ts
@@ -49,7 +49,7 @@ export const useAuthCommand = (
const errorMessage =
settings.merged.selectedAuthType ===
AuthType.LOGIN_WITH_GOOGLE_PERSONAL
- ? `Failed to login. Ensure your Google account is not an enterprise account.
+ ? `Failed to login. Ensure your Google account is not a Workspace account.
Message: ${getErrorMessage(e)}`
: `Failed to login. Message: ${getErrorMessage(e)}`;
setAuthError(errorMessage);
diff --git a/packages/cli/src/ui/hooks/useGeminiStream.ts b/packages/cli/src/ui/hooks/useGeminiStream.ts
index e045fdeb..86540b68 100644
--- a/packages/cli/src/ui/hooks/useGeminiStream.ts
+++ b/packages/cli/src/ui/hooks/useGeminiStream.ts
@@ -22,7 +22,7 @@ import {
GitService,
EditorType,
ThoughtSummary,
- isAuthError,
+ UnauthorizedError,
UserPromptEvent,
} from '@gemini-cli/core';
import { type Part, type PartListUnion } from '@google/genai';
@@ -537,7 +537,7 @@ export const useGeminiStream = (
'GEMINI_DEBUG: Caught error in useGeminiStream.ts:',
JSON.stringify(error),
);
- if (isAuthError(error)) {
+ if (error instanceof UnauthorizedError) {
onAuthError();
} else if (!isNodeError(error) || error.name !== 'AbortError') {
addItem(
diff --git a/packages/core/src/code_assist/errors.ts b/packages/core/src/code_assist/errors.ts
deleted file mode 100644
index f870ab5c..00000000
--- a/packages/core/src/code_assist/errors.ts
+++ /dev/null
@@ -1,13 +0,0 @@
-/**
- * @license
- * Copyright 2025 Google LLC
- * SPDX-License-Identifier: Apache-2.0
- */
-
-import { GaxiosError } from 'gaxios';
-
-export function isAuthError(error: unknown): boolean {
- return (
- error instanceof GaxiosError && error.response?.data?.error?.code === 401
- );
-}
diff --git a/packages/core/src/core/client.ts b/packages/core/src/core/client.ts
index e6d59db9..36acb3e8 100644
--- a/packages/core/src/core/client.ts
+++ b/packages/core/src/core/client.ts
@@ -292,8 +292,7 @@ export class GeminiClient {
throw error;
}
try {
- const parsedJson = JSON.parse(text);
- return parsedJson;
+ return JSON.parse(text);
} catch (parseError) {
await reportError(
parseError,
diff --git a/packages/core/src/core/turn.ts b/packages/core/src/core/turn.ts
index 85fffd93..cf5e0620 100644
--- a/packages/core/src/core/turn.ts
+++ b/packages/core/src/core/turn.ts
@@ -20,7 +20,7 @@ import { getResponseText } from '../utils/generateContentResponseUtilities.js';
import { reportError } from '../utils/errorReporting.js';
import { getErrorMessage } from '../utils/errors.js';
import { GeminiChat } from './geminiChat.js';
-import { isAuthError } from '../code_assist/errors.js';
+import { UnauthorizedError, toFriendlyError } from '../utils/errors.js';
// Define a structure for tools passed to the server
export interface ServerTool {
@@ -224,8 +224,9 @@ export class Turn {
value: { ...this.lastUsageMetadata, apiTimeMs: durationMs },
};
}
- } catch (error) {
- if (isAuthError(error)) {
+ } catch (e) {
+ const error = toFriendlyError(e);
+ if (error instanceof UnauthorizedError) {
throw error;
}
if (signal.aborted) {
diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts
index 163a4819..3a123452 100644
--- a/packages/core/src/index.ts
+++ b/packages/core/src/index.ts
@@ -21,7 +21,6 @@ export * from './core/nonInteractiveToolExecutor.js';
export * from './code_assist/codeAssist.js';
export * from './code_assist/oauth2.js';
-export * from './code_assist/errors.js';
// Export utilities
export * from './utils/paths.js';
diff --git a/packages/core/src/utils/errors.ts b/packages/core/src/utils/errors.ts
index 32139c1a..4787c439 100644
--- a/packages/core/src/utils/errors.ts
+++ b/packages/core/src/utils/errors.ts
@@ -4,6 +4,8 @@
* SPDX-License-Identifier: Apache-2.0
*/
+import { GaxiosError } from 'gaxios';
+
export function isNodeError(error: unknown): error is NodeJS.ErrnoException {
return error instanceof Error && 'code' in error;
}
@@ -11,12 +13,50 @@ export function isNodeError(error: unknown): error is NodeJS.ErrnoException {
export function getErrorMessage(error: unknown): string {
if (error instanceof Error) {
return error.message;
- } else {
- try {
- const errorMessage = String(error);
- return errorMessage;
- } catch {
- return 'Failed to get error details';
+ }
+ try {
+ return String(error);
+ } catch {
+ return 'Failed to get error details';
+ }
+}
+
+export class ForbiddenError extends Error {}
+export class UnauthorizedError extends Error {}
+export class BadRequestError extends Error {}
+
+interface ResponseData {
+ error?: {
+ code?: number;
+ message?: string;
+ };
+}
+
+export function toFriendlyError(error: unknown): unknown {
+ if (error instanceof GaxiosError) {
+ const data = parseResponseData(error);
+ if (data.error && data.error.message && data.error.code) {
+ switch (data.error.code) {
+ case 400:
+ return new BadRequestError(data.error.message);
+ case 401:
+ return new UnauthorizedError(data.error.message);
+ case 403:
+ // It's import to pass the message here since it might
+ // explain the cause like "the cloud project you're
+ // using doesn't have code assist enabled".
+ return new ForbiddenError(data.error.message);
+ default:
+ }
}
}
+ return error;
+}
+
+function parseResponseData(error: GaxiosError): ResponseData {
+ // Inexplicably, Gaxios sometimes doesn't JSONify the response data.
+ if (typeof error.response?.data === 'string') {
+ return JSON.parse(error.response?.data) as ResponseData;
+ }
+ return typeof error.response?.data as ResponseData;
}