nextauthjs/next-auth · error · Error

Unable to update authenticator with credential ${credentialI

Error message

Unable to update authenticator with credential ${credentialId}

What it means

The SurrealDB adapter's updateAuthenticator attempts to patch the authenticator record for the given credentialId; any failure (record not found, query error, mapper error) is swallowed by an empty catch and rethrown as this generic Error. It means the WebAuthn authenticator could not be persisted for that credential.

Source

Thrown at packages/adapter-surrealdb/src/index.ts:552

    async updateAuthenticatorCounter(
      credentialId: AdapterAuthenticator["credentialID"],
      newCounter: AdapterAuthenticator["counter"]
    ) {
      try {
        if (!credentialId) throw new Error("credential id is required")
        const surreal = await client
        const [authenticatorDoc] = await surreal.query<[AuthenticatorDoc]>(
          `UPDATE ONLY authenticator MERGE $doc WHERE credentialID = $cid`,
          {
            cid: credentialId,
            doc: {
              counter: newCounter,
            },
          }
        )
        return docToAuthenticator(authenticatorDoc)
      } catch {}
      throw Error(
        `Unable to update authenticator with credential ${credentialId}`
      )
    },
  }
}

View on GitHub (pinned to a1a16a5a77)

Solutions

  1. Verify the authenticator record exists in SurrealDB: SELECT * FROM authenticators WHERE credentialId = '...'
  2. Add logging inside the swallowed catch (or temporarily rethrow) to see the real underlying SurrealDB error
  3. Check table permissions allow UPDATE for the adapter's user/role
  4. Ensure the SurrealDB schema matches the adapter's expected authenticators table (counter, credentialPublicKey, credentialDeviceType, etc.)

Example fix

// before
} catch {}
throw Error(`Unable to update authenticator with credential ${credentialId}`)
// after
} catch (e) {
  console.error(`updateAuthenticator failed for ${credentialId}`, e)
  throw e
}
Defensive patterns

Strategy: try-catch

Validate before calling

const existing = await surreal.query(
  `SELECT * FROM authenticators WHERE credentialId = $id`, { id: credentialId }
)
if (!existing[0]?.result?.length) throw new Error(`Authenticator ${credentialId} not found`)

Type guard

function isAuthenticatorDoc(d: unknown): d is { credentialId: string; counter: number } {
  return typeof d === 'object' && d !== null && 'credentialId' in d
}

Try / catch

try {
  await updateAuthenticator(authenticator)
} catch (e) {
  if (e instanceof Error && e.message.includes('Unable to update authenticator')) {
    // inspect SurrealDB permissions/schema, then retry or re-register credential
  }
  throw e
}

Prevention

When it happens

Trigger: Calling SurrealDBAdapter's updateAuthenticator (invoked via the WebAuthn config flow) when the authenticator row for credentialId does not exist, the credentials table name/structure differs from what the adapter expects, or the SurrealDB UPDATE query fails (permissions, connection).

Common situations: WebAuthn credentials stored by another adapter/version with a different schema; SurrealDB table permissions (DEFINE TABLE ... PERMISSIONS) blocking updates; credentialId casing/format mismatch; SurrealDB instance migration leaving stale authenticator records.

Understand the failure class

Related errors


AI-assisted analysis of nextauthjs/next-auth@a1a16a5a77 (2026-08-28). Data as JSON: /api/errors/286c465c5aa8cb20. Report an issue: GitHub.