{"record":{"id":"c6c3dfbdab4ebb9e","repo":"google-gemini/gemini-cli","slug":"attempted-to-save-credentials-without-an-access-to","errorCode":null,"errorMessage":"Attempted to save credentials without an access token.","messagePattern":"Attempted to save credentials without an access token\\.","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/core/src/code_assist/oauth-credential-storage.ts","lineNumber":66,"sourceCode":"\n      // Fallback: Try to migrate from old file-based storage\n      return await this.migrateFromFileStorage();\n    } catch (error: unknown) {\n      coreEvents.emitFeedback(\n        'error',\n        'Failed to load OAuth credentials',\n        error,\n      );\n      throw new Error('Failed to load OAuth credentials', { cause: error });\n    }\n  }\n\n  /**\n   * Save OAuth credentials\n   */\n  static async saveCredentials(credentials: Credentials): Promise<void> {\n    if (!credentials.access_token) {\n      throw new Error('Attempted to save credentials without an access token.');\n    }\n\n    const existing = await this.storage.getCredentials(MAIN_ACCOUNT_KEY);\n    const mergedRefreshToken =\n      credentials.refresh_token || existing?.token.refreshToken;\n\n    // Convert Google Credentials to OAuthCredentials format\n    const mcpCredentials: OAuthCredentials = {\n      serverName: MAIN_ACCOUNT_KEY,\n      token: {\n        accessToken: credentials.access_token,\n        refreshToken: mergedRefreshToken || undefined,\n        tokenType: credentials.token_type || 'Bearer',\n        scope: credentials.scope || undefined,\n        expiresAt: credentials.expiry_date || undefined,\n      },\n      updatedAt: Date.now(),\n    };","sourceCodeStart":48,"sourceCodeEnd":84,"githubUrl":"https://github.com/google-gemini/gemini-cli/blob/5024443c7217464a66e98f80d73172a26440bd8f/packages/core/src/code_assist/oauth-credential-storage.ts#L48-L84","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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')."],"exampleFix":"// before — saving incomplete credentials\nawait OAuthCredentialStorage.saveCredentials({\n  refresh_token: 'rt_123',\n  // access_token missing\n});\n\n// after — ensure access_token is present\nif (!creds.access_token) {\n  throw new Error('Cannot save: OAuth token exchange did not produce an access token');\n}\nawait OAuthCredentialStorage.saveCredentials(creds);","handlingStrategy":"validation","validationCode":"// Validate credentials before saving\nfunction hasAccessToken(creds: Credentials): creds is Credentials & { access_token: string } {\n  return typeof creds.access_token === 'string' && creds.access_token.length > 0;\n}\n\nif (!hasAccessToken(credentials)) {\n  throw new Error('OAuth flow incomplete: no access token received.');\n}\nawait OAuthCredentialStorage.saveCredentials(credentials);","typeGuard":"function hasAccessToken(creds: Credentials): creds is Credentials & { access_token: string } {\n  return typeof creds.access_token === 'string' && creds.access_token.length > 0;\n}","tryCatchPattern":"try {\n  await OAuthCredentialStorage.saveCredentials(creds);\n} catch (e) {\n  if (e instanceof Error && e.message.includes('without an access token')) {\n    // Complete the token exchange first, then retry\n    const refreshed = await refreshAccessToken(creds.refresh_token!);\n    await OAuthCredentialStorage.saveCredentials(refreshed);\n  } else throw e;\n}","preventionTips":["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."],"tags":["oauth","credentials","validation","data-integrity"],"backgroundTag":null,"analyzedSha":"5024443c7217464a66e98f80d73172a26440bd8f","analyzedAt":"2026-08-12T06:01:53.711Z","schemaVersion":2},"datasetVersion":"2026-08-12T13:17:24.610Z"}