google-gemini/gemini-cli · error · Error

Attempted to save credentials without an access token.

Error message

Attempted to save credentials without an access token.

What it means

Thrown by OAuthCredentialStorage.saveCredentials() when the credentials object passed in has no access_token property. The access token is the primary credential used for API calls; a credentials object without one is incomplete and persisting it would create an invalid state that causes confusing failures on the next load. This is a data-integrity guard.

Source

Thrown at packages/core/src/code_assist/oauth-credential-storage.ts:66

      // Fallback: Try to migrate from old file-based storage
      return await this.migrateFromFileStorage();
    } catch (error: unknown) {
      coreEvents.emitFeedback(
        'error',
        'Failed to load OAuth credentials',
        error,
      );
      throw new Error('Failed to load OAuth credentials', { cause: error });
    }
  }

  /**
   * Save OAuth credentials
   */
  static async saveCredentials(credentials: Credentials): Promise<void> {
    if (!credentials.access_token) {
      throw new Error('Attempted to save credentials without an access token.');
    }

    const existing = await this.storage.getCredentials(MAIN_ACCOUNT_KEY);
    const mergedRefreshToken =
      credentials.refresh_token || existing?.token.refreshToken;

    // Convert Google Credentials to OAuthCredentials format
    const mcpCredentials: OAuthCredentials = {
      serverName: MAIN_ACCOUNT_KEY,
      token: {
        accessToken: credentials.access_token,
        refreshToken: mergedRefreshToken || undefined,
        tokenType: credentials.token_type || 'Bearer',
        scope: credentials.scope || undefined,
        expiresAt: credentials.expiry_date || undefined,
      },
      updatedAt: Date.now(),
    };

View on GitHub (pinned to 5024443c72)

Solutions

  1. Verify credentials.access_token is a non-empty string before calling saveCredentials.
  2. Ensure the OAuth flow has fully completed (token exchange done) before persisting.
  3. If only a refresh token is available, complete the token refresh first to obtain an access token.
  4. Add an upstream guard: if (!creds.access_token) throw new Error('OAuth flow incomplete: no access token').

Example fix

// before — saving incomplete credentials
await OAuthCredentialStorage.saveCredentials({
  refresh_token: 'rt_123',
  // access_token missing
});

// after — ensure access_token is present
if (!creds.access_token) {
  throw new Error('Cannot save: OAuth token exchange did not produce an access token');
}
await OAuthCredentialStorage.saveCredentials(creds);
Defensive patterns

Strategy: validation

Validate before calling

// Validate credentials before saving
function hasAccessToken(creds: Credentials): creds is Credentials & { access_token: string } {
  return typeof creds.access_token === 'string' && creds.access_token.length > 0;
}

if (!hasAccessToken(credentials)) {
  throw new Error('OAuth flow incomplete: no access token received.');
}
await OAuthCredentialStorage.saveCredentials(credentials);

Type guard

function hasAccessToken(creds: Credentials): creds is Credentials & { access_token: string } {
  return typeof creds.access_token === 'string' && creds.access_token.length > 0;
}

Try / catch

try {
  await OAuthCredentialStorage.saveCredentials(creds);
} catch (e) {
  if (e instanceof Error && e.message.includes('without an access token')) {
    // Complete the token exchange first, then retry
    const refreshed = await refreshAccessToken(creds.refresh_token!);
    await OAuthCredentialStorage.saveCredentials(refreshed);
  } else throw e;
}

Prevention

When it happens

Trigger: Calling OAuthCredentialStorage.saveCredentials(creds) where creds.access_token is falsy (undefined, null, or empty string). This typically happens when the OAuth flow returned a credentials object before the token exchange completed, or when a refresh token response was mistaken for a full credentials payload.

Common situations: Saving a refresh-token-only response from an OAuth flow that didn't complete the token exchange; passing a partially-constructed credentials object; a race condition where credentials are saved before the authorization callback populates the access token; deserializing credentials from a truncated or malformed JSON source.

Related errors


AI-assisted analysis of google-gemini/gemini-cli@5024443c72 (2026-08-12). Data as JSON: /api/errors/c6c3dfbdab4ebb9e. Report an issue: GitHub.