microg/GmsCore · error · RuntimeException
Invalid result
Error message
Invalid result
What it means
In HybridAuthenticateActivity.handleMakeCredential, after deserializing the returned PublicKeyCredential, the code attempts a safe cast of its response to AuthenticatorAttestationResponse; if the cast fails (it is not an attestation response) it throws RuntimeException("Invalid result"). The result payload exists but has the wrong response type for a make-credential flow.
Source
Thrown at play-services-fido/core/src/main/kotlin/org/microg/gms/fido/core/ui/hybrid/HybridAuthenticateActivity.kt:177
val browserOptions = BrowserPublicKeyCredentialCreationOptions.Builder()
.setPublicKeyCredentialCreationOptions(publicKeyCredentialCreationOptions)
.setOrigin("https://${request.rp.id}".toUri())
.setClientDataHash(request.clientDataHash).build()
val intent = Intent(this, AuthenticatorActivity::class.java)
.putExtra(AuthenticatorActivity.KEY_SOURCE, AuthenticatorActivity.SOURCE_HYBRID)
.putExtra(AuthenticatorActivity.KEY_TYPE, AuthenticatorActivity.TYPE_REGISTER)
.putExtra(AuthenticatorActivity.KEY_OPTIONS, browserOptions.serializeToBytes())
val result = suspendCancellableCoroutine { continuation ->
waitingLauncherContinuation = continuation
waitingLauncher.launch(intent)
}
val resultBytes = result.data?.getByteArrayExtra(FIDO2_KEY_CREDENTIAL_EXTRA) ?: throw RuntimeException("No result")
val publicKeyCredential = PublicKeyCredential.deserializeFromBytes(resultBytes)
val response = publicKeyCredential.response as? AuthenticatorAttestationResponse? ?: throw RuntimeException("Invalid result")
val attestationObject = AttestationObject.decode(response.attestationObject)
return AuthenticatorMakeCredentialResponse(
authData = attestationObject.authData,
fmt = attestationObject.fmt,
attStmt = attestationObject.attStmt
)
}
private suspend fun handleGetAssertion(request: AuthenticatorGetAssertionRequest): AuthenticatorGetAssertionResponse {
val publicKeyCredentialRequestOptions = PublicKeyCredentialRequestOptions.Builder()
.setRpId(request.rpId)
.setChallenge(request.clientDataHash)
.setAllowList(request.allowList)
.setRequireUserVerification(request.options?.userVerification?.takeIf { it }?.let { UserVerificationRequirement.REQUIRED })
.build()
val browserOptions = BrowserPublicKeyCredentialRequestOptions.Builder()View on GitHub (pinned to 157c9d86ac)
Solutions
- Verify the launched intent requests a make-credential (register) operation, not get-assertion
- Log publicKeyCredential.response::class to see the actual type returned
- Ensure the FIDO request/options passed to the inner activity are PublicKeyCredentialCreationOptions, not RequestOptions
- Update microG so response deserialization matches the request type
Example fix
// before
val response = publicKeyCredential.response as? AuthenticatorAttestationResponse? ?: throw RuntimeException("Invalid result")
// after
val response = publicKeyCredential.response as? AuthenticatorAttestationResponse
?: run {
Log.e(TAG, "Expected attestation response, got ${publicKeyCredential.response?.javaClass}")
throw RuntimeException("Invalid result")
} Defensive patterns
Strategy: type-guard
Validate before calling
require(publicKeyCredential.response is AuthenticatorAttestationResponse) {
"Expected attestation response, got ${publicKeyCredential.response?.javaClass}"
} Type guard
fun PublicKeyCredential.isAttestation(): Boolean =
response is AuthenticatorAttestationResponse Try / catch
try {
val response = handleMakeCredential()
} catch (e: RuntimeException) {
if (e.message == "Invalid result") emit(FidoResult.WrongResponseType) else throw e
} Prevention
- Launch sign-up flows with make-credential options only
- Verify the serialized credential version matches between activities
- Log the response class when the cast fails
- Keep microG FIDO components on matching versions
When it happens
Trigger: The nested activity returned a credential whose response is an AuthenticatorAssertionResponse (get-assertion) instead of AuthenticatorAttestationResponse, or deserialization produced a response of an unexpected subtype.
Common situations: Mixing up sign-in and sign-up intents when launching the hybrid flow; a library version where deserializeFromBytes yields a different response class; the inner activity short-circuits with a previously-registered credential.
Related errors
- DATA_ERR
- Transport ${transport} not supported
- Attachment ${attachment} not supported
- Attestation conveyance preference ${attachment} not supporte
- No response set.
AI-assisted analysis of microg/GmsCore@157c9d86ac (2026-09-06).
Data as JSON: /api/errors/b6eda0ceca437be3.
Report an issue: GitHub.