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
- Verify credentials.access_token is a non-empty string before calling saveCredentials.
- Ensure the OAuth flow has fully completed (token exchange done) before persisting.
- If only a refresh token is available, complete the token refresh first to obtain an access token.
- 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
- Always verify access_token is present after the OAuth token exchange.
- Never save partial credentials objects from incomplete OAuth responses.
- Add a type guard on the credentials object before persistence.
- Log when credentials are missing access_token to trace the source.
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
- Failed to load OAuth credentials
- Failed to clear OAuth credentials
- ${originalMessage}. The initial COMPUTE_ADC attempt also fai
- Invalid taskId: ${taskId}
- Security violation: Null byte detected in path.
AI-assisted analysis of google-gemini/gemini-cli@5024443c72 (2026-08-12).
Data as JSON: /api/errors/c6c3dfbdab4ebb9e.
Report an issue: GitHub.