fix(core): explicitly set error names to avoid bundling renaming issues (#23913)

This commit is contained in:
Yuna Seol
2026-03-26 23:40:49 -04:00
committed by GitHub
parent 6f92642524
commit aca8e1af05
4 changed files with 60 additions and 17 deletions

View File

@@ -19,6 +19,7 @@ import {
debugLogger,
coreEvents,
getErrorMessage,
getErrorType,
} from '@google/gemini-cli-core';
import { runSyncCleanup } from './cleanup.js';
@@ -82,7 +83,7 @@ export function handleError(
timestamp: new Date().toISOString(),
status: 'error',
error: {
type: error instanceof Error ? error.constructor.name : 'Error',
type: getErrorType(error),
message: errorMessage,
},
stats: streamFormatter.convertToStreamStats(metrics, 0),
@@ -177,7 +178,7 @@ export function handleCancellationError(config: Config): never {
timestamp: new Date().toISOString(),
status: 'error',
error: {
type: 'FatalCancellationError',
type: getErrorType(cancellationError),
message: cancellationError.message,
},
stats: streamFormatter.convertToStreamStats(metrics, 0),
@@ -218,7 +219,7 @@ export function handleMaxTurnsExceededError(config: Config): never {
timestamp: new Date().toISOString(),
status: 'error',
error: {
type: 'FatalTurnLimitedError',
type: getErrorType(maxTurnsError),
message: maxTurnsError.message,
},
stats: streamFormatter.convertToStreamStats(metrics, 0),

View File

@@ -32,6 +32,7 @@ export class ProjectIdRequiredError extends Error {
super(
'This account requires setting the GOOGLE_CLOUD_PROJECT or GOOGLE_CLOUD_PROJECT_ID env var. See https://goo.gle/gemini-cli-auth-docs#workspace-gca',
);
this.name = 'ProjectIdRequiredError';
}
}
@@ -42,6 +43,7 @@ export class ProjectIdRequiredError extends Error {
export class ValidationCancelledError extends Error {
constructor() {
super('User cancelled account validation');
this.name = 'ValidationCancelledError';
}
}
@@ -51,6 +53,7 @@ export class IneligibleTierError extends Error {
constructor(ineligibleTiers: IneligibleTier[]) {
const reasons = ineligibleTiers.map((t) => t.reasonMessage).join(', ');
super(reasons);
this.name = 'IneligibleTierError';
this.ineligibleTiers = ineligibleTiers;
}
}

View File

@@ -355,12 +355,29 @@ describe('getErrorType', () => {
expect(getErrorType(undefined)).toBe('unknown');
});
it('should strip leading underscores from error names', () => {
class _GaxiosError extends Error {}
it('should use explicitly set error names', () => {
class _GaxiosError extends Error {
constructor(message: string) {
super(message);
this.name = 'GaxiosError';
}
}
expect(getErrorType(new _GaxiosError('test'))).toBe('GaxiosError');
const errorWithUnderscoreName = new Error('test');
errorWithUnderscoreName.name = '_CodeBuddyError';
expect(getErrorType(errorWithUnderscoreName)).toBe('CodeBuddyError');
class BadRequestError3 extends Error {
constructor(message: string) {
super(message);
this.name = 'BadRequestError';
}
}
expect(getErrorType(new BadRequestError3('test'))).toBe('BadRequestError');
class _AbortError2 extends Error {
constructor(message: string) {
super(message);
this.name = 'AbortError';
}
}
expect(getErrorType(new _AbortError2('test'))).toBe('AbortError');
});
});

View File

@@ -57,9 +57,11 @@ export function getErrorMessage(error: unknown): string {
export function getErrorType(error: unknown): string {
if (!(error instanceof Error)) return 'unknown';
// Return constructor name if the generic 'Error' name is used (for custom errors)
// Use the constructor name if the standard error name is missing or generic.
const name =
error.name === 'Error' ? (error.constructor?.name ?? 'Error') : error.name;
error.name && error.name !== 'Error'
? error.name
: (error.constructor?.name ?? 'Error');
// Strip leading underscore from error names. Bundlers like esbuild sometimes
// rename classes to avoid scope collisions.
@@ -72,42 +74,50 @@ export class FatalError extends Error {
readonly exitCode: number,
) {
super(message);
this.name = 'FatalError';
}
}
export class FatalAuthenticationError extends FatalError {
constructor(message: string) {
super(message, 41);
this.name = 'FatalAuthenticationError';
}
}
export class FatalInputError extends FatalError {
constructor(message: string) {
super(message, 42);
this.name = 'FatalInputError';
}
}
export class FatalSandboxError extends FatalError {
constructor(message: string) {
super(message, 44);
this.name = 'FatalSandboxError';
}
}
export class FatalConfigError extends FatalError {
constructor(message: string) {
super(message, 52);
this.name = 'FatalConfigError';
}
}
export class FatalTurnLimitedError extends FatalError {
constructor(message: string) {
super(message, 53);
this.name = 'FatalTurnLimitedError';
}
}
export class FatalToolExecutionError extends FatalError {
constructor(message: string) {
super(message, 54);
this.name = 'FatalToolExecutionError';
}
}
export class FatalCancellationError extends FatalError {
constructor(message: string) {
super(message, 130); // Standard exit code for SIGINT
this.name = 'FatalCancellationError';
}
}
@@ -118,7 +128,12 @@ export class CanceledError extends Error {
}
}
export class ForbiddenError extends Error {}
export class ForbiddenError extends Error {
constructor(message: string) {
super(message);
this.name = 'ForbiddenError';
}
}
export class AccountSuspendedError extends ForbiddenError {
readonly appealUrl?: string;
readonly appealLinkText?: string;
@@ -130,8 +145,18 @@ export class AccountSuspendedError extends ForbiddenError {
this.appealLinkText = metadata?.['appeal_url_link_text'];
}
}
export class UnauthorizedError extends Error {}
export class BadRequestError extends Error {}
export class UnauthorizedError extends Error {
constructor(message: string) {
super(message);
this.name = 'UnauthorizedError';
}
}
export class BadRequestError extends Error {
constructor(message: string) {
super(message);
this.name = 'BadRequestError';
}
}
export class ChangeAuthRequestedError extends Error {
constructor() {
@@ -264,10 +289,7 @@ export function isAuthenticationError(error: unknown): boolean {
}
// Check for UnauthorizedError class (from MCP SDK or our own)
if (
error instanceof Error &&
error.constructor.name === 'UnauthorizedError'
) {
if (error instanceof Error && error.name === 'UnauthorizedError') {
return true;
}