{"record":{"id":"92fa6c6b81d31bd9","repo":"microg/GmsCore","slug":"oauthtoken-is-null-92fa6c","errorCode":null,"errorMessage":"oauthToken is null","messagePattern":"oauthToken is null","errorType":"exception","errorClass":"RuntimeException","httpStatus":null,"severity":"error","filePath":"play-services-core/src/main/kotlin/com/google/android/gms/semanticlocationhistory/extensions.kt","lineNumber":50,"sourceCode":"const val E2EE_TYPE_URL = \"type.googleapis.com/geller.oneplatform.GellerE2eeElement\"\nconst val AES_GCM_IV_SIZE = 12\nconst val AES_GCM_TAG_BITS = 128\nconst val AES_KEY_SIZE = 32\n\nconst val SEMANTIC_TYPE_HOME = 1\nconst val SEMANTIC_TYPE_WORK = 2\nconst val SEARCH_WINDOW_DAYS = 30L\nconst val SECONDS_PER_DAY = 86400L\n\nsuspend fun Context.requestGellerOauthToken(accountName: String, scope: String = \"oauth2:https://www.googleapis.com/auth/webhistory\"): String {\n    val accountManager = AccountManager.get(this)\n    val account = accountManager.getAccountsByType(AuthConstants.DEFAULT_ACCOUNT_TYPE).find {\n        it.name == accountName\n    }\n    if (account == null) throw RuntimeException(\"account is null\")\n    return withContext(Dispatchers.IO) {\n        accountManager.blockingGetAuthToken(account, scope, true)\n    } ?: throw RuntimeException(\"oauthToken is null\")\n}\n\nsuspend fun Context.getObfuscatedGaiaId(accountName: String) = requestGellerOauthToken(accountName, AuthConstants.SCOPE_GET_ACCOUNT_ID)\n\nprivate val weakCachedKeyMap = WeakHashMap<String, List<Pair<ByteArray, Int>>>()\n\nfun loadKeyMaterials(context: Context, accountName: String): List<Pair<ByteArray, Int>> {\n    val result = weakCachedKeyMap.get(accountName)\n    if (!result.isNullOrEmpty()) {\n        return result\n    }\n    val keyManager = LocalKeyManager.getInstance(context)\n    val keys = keyManager.getKeysForDomain(accountName, ODLH_SECURITY_DOMAIN)\n        .ifEmpty { keyManager.getKeysForDomain(accountName, ODLH_SECURITY_DOMAIN_SHORT) }\n    val materials = keys.mapNotNull { key ->\n        val material = key.keyMaterial?.toByteArray() ?: return@mapNotNull null\n        if (material.size != AES_KEY_SIZE) return@mapNotNull null\n        material to (key.keyVersion ?: 0)","sourceCodeStart":32,"sourceCodeEnd":68,"githubUrl":"https://github.com/microg/GmsCore/blob/157c9d86ac46c195a86c2f15ab55c84036223f95/play-services-core/src/main/kotlin/com/google/android/gms/semanticlocationhistory/extensions.kt#L32-L68","documentation":"The same requestGellerOauthToken function throws RuntimeException(\"oauthToken is null\") when AccountManager.blockingGetAuthToken returns null for a valid account — the account exists but no auth token could be issued for the webhistory scope.","triggerScenarios":"blockingGetAuthToken returns null: the account's auth session is expired, the user has not granted the auth scope, the account needs re-authentication (e.g. after password change), or AccountManager could not contact the auth backend (network/timeout is usually surfaced differently, but null can occur).","commonSituations":"Devices with stale Google credentials, accounts flagged for re-authentication, or the geller/webhistory scope being denied for the account on microG.","solutions":["Invalidate the stale token (accountManager.invalidateAuthToken) and retry the request","Check the account's auth state and prompt the user to sign in again","Ensure microG's auth backend can reach Google servers (network/proxy checks)","Catch RuntimeException and fall back to an unauthenticated / degraded feature path"],"exampleFix":"// before\nval token = context.requestGellerOauthToken(accountName)\n// after\nval token = try {\n    context.requestGellerOauthToken(accountName)\n} catch (e: RuntimeException) {\n    Log.w(TAG, \"geller oauth token unavailable\", e); null\n}","handlingStrategy":"retry","validationCode":"val account = AccountManager.get(context).findAccount(accountName) ?: return null\nval hasAuth = true // ensure account is not flagged for re-auth in account settings","typeGuard":"suspend fun Context.safeGellerToken(accountName: String): String? =\n    try { requestGellerOauthToken(accountName) } catch (e: RuntimeException) { null }","tryCatchPattern":"try {\n    val token = context.requestGellerOauthToken(accountName)\n} catch (e: RuntimeException) {\n    accountManager.invalidateAuthToken(account.type, null)\n    retryWithReauth()\n}","preventionTips":["Invalidate and refresh tokens when auth calls start returning null","Prompt re-authentication after password changes","Check network reachability of the auth backend before token requests"],"tags":["android","oauth","account-manager"],"backgroundTag":"authentication-required","analyzedSha":"157c9d86ac46c195a86c2f15ab55c84036223f95","analyzedAt":"2026-09-06T17:27:33.892Z","contentChangedAt":"2026-09-06T17:27:33.892Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}