microg/GmsCore · error · IllegalArgumentException

invalid handle

Error message

invalid handle

What it means

RecaptchaGuardImpl.execute() validates that the RecaptchaHandle passed in belongs to the calling app. If the handle carries a non-null clientPackageName that differs from the package name this service instance was created for, it throws IllegalArgumentException("invalid handle"). The handle is bound to the app that called init(); handles cannot be reused across apps or processes.

Source

Thrown at play-services-recaptcha/core/src/main/kotlin/org/microg/gms/recaptcha/RecaptchaGuardImpl.kt:48

class RecaptchaGuardImpl(private val context: Context, private val packageName: String) : RecaptchaImpl {
    private val queue = singleInstanceOf { Volley.newRequestQueue(context.applicationContext) }
    private var lastToken: String? = null

    override suspend fun init(params: InitParams): RecaptchaHandle {
        val response = ProtobufPostRequest(
            "https://www.recaptcha.net/recaptcha/api3/ac", RecaptchaInitRequest(
                data_ = RecaptchaInitRequest.Data(
                    siteKey = params.siteKey,
                    packageName = packageName,
                    version = "${VersionUtil(context).versionCode};${params.version}"
                )
            ), RecaptchaInitResponse.ADAPTER
        ).sendAndAwait(queue)
        lastToken = response.token
        return RecaptchaHandle(params.siteKey, packageName, response.acceptableAdditionalArgs.toList())
    }

    override suspend fun execute(params: ExecuteParams): RecaptchaResultData {
        if (params.handle.clientPackageName != null && params.handle.clientPackageName != packageName) throw IllegalArgumentException("invalid handle")
        val timestamp = System.currentTimeMillis()
        val additionalArgs = mutableMapOf<String, String>()
        val guardMap = mutableMapOf<String, String>()
        for (key in params.action.additionalArgs.keySet()) {
            val value = params.action.additionalArgs.getString(key)
                ?: throw Exception("Only string values are allowed as an additional arg in RecaptchaAction")
            if (key !in params.handle.acceptableAdditionalArgs)
                throw Exception("AdditionalArgs key[ \"$key\" ] is not accepted by reCATPCHA server")
            additionalArgs.put(key, value)
        }
        Log.d(TAG, "Additional arguments: $additionalArgs")
        if (lastToken == null) {
            init(InitParams().apply { siteKey = params.handle.siteKey; version = params.version })
        }
        val token = lastToken!!
        guardMap["token"] = token
        guardMap["action"] = params.action.toString()

View on GitHub (pinned to 157c9d86ac)

Solutions

  1. Ensure the handle used in execute() came from init() in the same app/process with the same package name
  2. Check that RecaptchaHandle.clientPackageName is null or equals your packageName before calling execute
  3. Re-call init() to obtain a fresh handle for the current app
  4. Verify no code is deserializing an old handle from storage with a wrong or legacy package name

Example fix

// before
val result = guard.execute(executeParams) // executeParams.handle.clientPackageName = "com.other.app"
// after
if (executeParams.handle.clientPackageName == null || executeParams.handle.clientPackageName == packageName) {
    val result = guard.execute(executeParams)
} else {
    val handle = guard.init(initParams) // fresh handle for this app
    val result = guard.execute(executeParams.copy(handle = handle))
}
Defensive patterns

Strategy: validation

Validate before calling

require(handle.clientPackageName == null || handle.clientPackageName == packageName) { "handle not owned by this app" }

Type guard

fun RecaptchaHandle.isOwnedBy(pkg: String): Boolean = clientPackageName == null || clientPackageName == pkg

Try / catch

try { guard.execute(params) } catch (e: IllegalArgumentException) { /* re-init handle */ }

Prevention

When it happens

Trigger: Calling execute() with a handle whose clientPackageName field is set and does not equal the packageName of the current RecaptchaGuardImpl — e.g. a handle obtained by another app, a stale/parceled handle from a different uid, or a hand-crafted handle in tests.

Common situations: App passes a handle received from a different Google Play services account/process, reuses a serialized handle after app reinstall (package identity check fails), or a test constructs RecaptchaHandle with the wrong clientPackageName.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


AI-assisted analysis of microg/GmsCore@157c9d86ac (2026-09-06). Data as JSON: /api/errors/bdc3dea07c0c3116. Report an issue: GitHub.