{"record":{"id":"53d1836791a5125b","repo":"microg/GmsCore","slug":"oauthtoken-is-null","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/family/v2/manage/FamilyExtensions.kt","lineNumber":126,"sourceCode":"    val deviceInfo = DeviceInfo.build {\n        moduleVersion(FAMILY_MANAGEMENT_MODULE_VERSION)\n        clientType(7)\n        moduleInfo(CallerInfo.build { appId(appId) })\n    }\n    return RequestContext.build {\n        deviceInfo(deviceInfo)\n        familyExperimentOverrides(\"\")\n        moduleSet(\"\")\n    }\n}\n\nsuspend fun requestOauthToken(context: Context, accountName: String, service: String): String {\n    val authResponse = withContext(Dispatchers.IO) {\n        AuthManager(\n            context, accountName, Constants.GMS_PACKAGE_NAME, service\n        ).apply { isPermitted = true }.requestAuth(true)\n    }\n    return authResponse.auth ?: throw RuntimeException(\"oauthToken is null\")\n}\n\nfun GetFamilyResponse.parseToMemberDataModels(context: Context, accountName: String, configResponse: GetFamilyManagementConfigResponse?): MutableList<MemberDataModel> {\n    val inviteSlotSize = configResponse?.let {\n        val inviteOption = it.configMain?.familyOption?.find { option -> option.optionId == FAMILY_OPTION_INVITE_ID }\n        val inviteSlotsContent = inviteOption?.optionContents?.find { c -> c.optId == FAMILY_OPTION_INVITE_TITLE_ID }?.content\n        inviteSlotsContent?.let { content -> Regex(\"\\\\d+\").find(content)?.value?.toIntOrNull() ?: 0 } ?: 0\n    } ?: 0\n    val memberDataModels = mutableListOf<MemberDataModel>()\n    memberDataList.map {\n        MemberDataModel().apply {\n            memberId = it.memberId ?: \"\"\n            profilePhotoUrl = it.profile?.profilePhotoUrl ?: it.profile?.defaultPhotoUrl ?: \"\"\n            displayName = it.profile?.displayName ?: it.profile?.email ?: \"\"\n            email = it.profile?.email ?: \"\"\n            hohGivenName = it.hohGivenName ?: \"\"\n            role = it.role?.value ?: FamilyRole.UNCONFIRMED_MEMBER.value\n            roleName = it.role?.name ?: FamilyRole.UNCONFIRMED_MEMBER.name","sourceCodeStart":108,"sourceCodeEnd":144,"githubUrl":"https://github.com/microg/GmsCore/blob/157c9d86ac46c195a86c2f15ab55c84036223f95/play-services-core/src/main/kotlin/com/google/android/gms/family/v2/manage/FamilyExtensions.kt#L108-L144","documentation":"requestOauthToken performs an auth request via AuthManager and returns the resulting auth token. If the auth response contains no token (auth == null), it throws RuntimeException(\"oauthToken is null\") — meaning authentication completed (or silently failed) without yielding a token for the requested service scope.","triggerScenarios":"AuthManager.requestAuth returns a response whose auth field is null: the account is not signed in, the requested service/scope is not permitted for the account, network/server issues returned an empty token, or isPermitted=true was granted but the backend refused to issue a token.","commonSituations":"Family management screens in microG when the configured Google account is not properly registered with microG's auth backend, or the FAMILY_SCOPE service string is rejected server-side.","solutions":["Verify the account exists in microG and can authenticate (check microG account settings / Google sign-in)","Check the AuthResponse for error details from requestAuth before reading auth","Retry after confirming network connectivity to Google auth servers","Catch the RuntimeException in callers (e.g. FamilyViewModel already wraps it in runCatching) and surface a sign-in-required state"],"exampleFix":"// before\nval token = requestOauthToken(context, account, SERVICE_FAMILY_SCOPE)\n// after\nval token = try {\n    requestOauthToken(context, account, SERVICE_FAMILY_SCOPE)\n} catch (e: RuntimeException) {\n    Log.w(TAG, \"oauth token unavailable\", e); return\n}","handlingStrategy":"try-catch","validationCode":"val accounts = AccountManager.get(context).getAccountsByType(AuthConstants.DEFAULT_ACCOUNT_TYPE)\nrequire(accounts.any { it.name == accountName }) { \"account not signed in: $accountName\" }","typeGuard":"fun Context.hasSignedInAccount(accountName: String): Boolean =\n    AccountManager.get(this).getAccountsByType(AuthConstants.DEFAULT_ACCOUNT_TYPE).any { it.name == accountName }","tryCatchPattern":"try {\n    val token = requestOauthToken(context, accountName, scope)\n} catch (e: RuntimeException) {\n    showSignInRequired()\n}","preventionTips":["Verify the account is registered in microG before token requests","Check AuthResponse error fields to distinguish auth failures from null tokens","Handle sign-in-expired states in the UI"],"tags":["android","oauth","authentication"],"backgroundTag":"authentication-required","analyzedSha":"157c9d86ac46c195a86c2f15ab55c84036223f95","analyzedAt":"2026-09-06T17:27:33.892Z","contentChangedAt":"2026-09-06T17:27:33.892Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}