Index: src/wp-admin/includes/ajax-actions.php
===================================================================
--- src/wp-admin/includes/ajax-actions.php	(revision 38182)
+++ src/wp-admin/includes/ajax-actions.php	(working copy)
@@ -3345,16 +3345,25 @@
 		wp_send_json_error( $status );
 	}
 
-	$upgrader = new Theme_Upgrader( new Automatic_Upgrader_Skin() );
+	$skin     = new WP_Ajax_Upgrader_Skin();
+	$upgrader = new Theme_Upgrader( $skin );
 	$result   = $upgrader->install( $api->download_link );
 
 	if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
-		$status['debug'] = $upgrader->skin->get_upgrade_messages();
+		$status['debug'] = $skin->get_upgrade_messages();
 	}
 
 	if ( is_wp_error( $result ) ) {
+		$status['errorCode']    = $result->get_error_code();
 		$status['errorMessage'] = $result->get_error_message();
 		wp_send_json_error( $status );
+	} elseif ( is_wp_error( $skin->result ) ) {
+		$status['errorCode']    = $skin->result->get_error_code();
+		$status['errorMessage'] = $skin->result->get_error_message();
+		wp_send_json_error( $status );
+	} elseif ( $skin->get_errors()->get_error_code() ) {
+		$status['errorMessage'] = $skin->get_error_messages();
+		wp_send_json_error( $status );
 	} elseif ( is_null( $result ) ) {
 		global $wp_filesystem;
 
@@ -3363,6 +3372,7 @@
 
 		// Pass through the error from WP_Filesystem if one was raised.
 		if ( $wp_filesystem instanceof WP_Filesystem_Base && is_wp_error( $wp_filesystem->errors ) && $wp_filesystem->errors->get_error_code() ) {
+			$status['errorCode']    = $wp_filesystem->errors->get_error_code();
 			$status['errorMessage'] = esc_html( $wp_filesystem->errors->get_error_message() );
 		}
 
@@ -3437,14 +3447,26 @@
 		wp_update_themes();
 	}
 
-	$upgrader = new Theme_Upgrader( new Automatic_Upgrader_Skin() );
+	$skin     = new WP_Ajax_Upgrader_Skin();
+	$upgrader = new Theme_Upgrader( $skin );
 	$result   = $upgrader->bulk_upgrade( array( $stylesheet ) );
 
 	if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
-		$status['debug'] = $upgrader->skin->get_upgrade_messages();
+		$status['debug'] = $skin->get_upgrade_messages();
 	}
 
-	if ( is_array( $result ) && ! empty( $result[ $stylesheet ] ) ) {
+	if ( is_wp_error( $result ) ) {
+		$status['errorCode']    = $result->get_error_code();
+		$status['errorMessage'] = $result->get_error_message();
+		wp_send_json_error( $status );
+	} elseif ( is_wp_error( $skin->result ) ) {
+		$status['errorCode']    = $skin->result->get_error_code();
+		$status['errorMessage'] = $skin->result->get_error_message();
+		wp_send_json_error( $status );
+	} elseif ( $skin->get_errors()->get_error_code() ) {
+		$status['errorMessage'] = $skin->get_error_messages();
+		wp_send_json_error( $status );
+	} elseif ( is_array( $result ) && ! empty( $result[ $stylesheet ] ) ) {
 
 		// Theme is already at the latest version.
 		if ( true === $result[ $stylesheet ] ) {
@@ -3458,10 +3480,6 @@
 		}
 
 		wp_send_json_success( $status );
-	} elseif ( is_wp_error( $upgrader->skin->result ) ) {
-		$status['errorCode']    = $upgrader->skin->result->get_error_code();
-		$status['errorMessage'] = $upgrader->skin->result->get_error_message();
-		wp_send_json_error( $status );
 	} elseif ( false === $result ) {
 		global $wp_filesystem;
 
@@ -3470,6 +3488,7 @@
 
 		// Pass through the error from WP_Filesystem if one was raised.
 		if ( $wp_filesystem instanceof WP_Filesystem_Base && is_wp_error( $wp_filesystem->errors ) && $wp_filesystem->errors->get_error_code() ) {
+			$status['errorCode']    = $wp_filesystem->errors->get_error_code();
 			$status['errorMessage'] = esc_html( $wp_filesystem->errors->get_error_message() );
 		}
 
@@ -3594,16 +3613,25 @@
 
 	$status['pluginName'] = $api->name;
 
-	$upgrader = new Plugin_Upgrader( new Automatic_Upgrader_Skin() );
+	$skin     = new WP_Ajax_Upgrader_Skin();
+	$upgrader = new Plugin_Upgrader( $skin );
 	$result   = $upgrader->install( $api->download_link );
 
 	if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
-		$status['debug'] = $upgrader->skin->get_upgrade_messages();
+		$status['debug'] = $skin->get_upgrade_messages();
 	}
 
 	if ( is_wp_error( $result ) ) {
+		$status['errorCode']    = $result->get_error_code();
 		$status['errorMessage'] = $result->get_error_message();
 		wp_send_json_error( $status );
+	} elseif ( is_wp_error( $skin->result ) ) {
+		$status['errorCode']    = $skin->result->get_error_code();
+		$status['errorMessage'] = $skin->result->get_error_message();
+		wp_send_json_error( $status );
+	} elseif ( $skin->get_errors()->get_error_code() ) {
+		$status['errorMessage'] = $skin->get_error_messages();
+		wp_send_json_error( $status );
 	} elseif ( is_null( $result ) ) {
 		global $wp_filesystem;
 
@@ -3612,6 +3640,7 @@
 
 		// Pass through the error from WP_Filesystem if one was raised.
 		if ( $wp_filesystem instanceof WP_Filesystem_Base && is_wp_error( $wp_filesystem->errors ) && $wp_filesystem->errors->get_error_code() ) {
+			$status['errorCode']    = $wp_filesystem->errors->get_error_code();
 			$status['errorMessage'] = esc_html( $wp_filesystem->errors->get_error_message() );
 		}
 
@@ -3680,19 +3709,26 @@
 
 	wp_update_plugins();
 
-	$skin     = new Automatic_Upgrader_Skin();
+	$skin     = new WP_Ajax_Upgrader_Skin();
 	$upgrader = new Plugin_Upgrader( $skin );
 	$result   = $upgrader->bulk_upgrade( array( $plugin ) );
 
 	if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
-		$status['debug'] = $upgrader->skin->get_upgrade_messages();
-	}
-
-	if ( is_array( $result ) && empty( $result[ $plugin ] ) && is_wp_error( $skin->result ) ) {
-		$result = $skin->result;
+		$status['debug'] = $skin->get_upgrade_messages();
 	}
 
-	if ( is_array( $result ) && ! empty( $result[ $plugin ] ) ) {
+	if ( is_wp_error( $result ) ) {
+		$status['errorCode']    = $result->get_error_code();
+		$status['errorMessage'] = $result->get_error_message();
+		wp_send_json_error( $status );
+	} elseif ( is_wp_error( $skin->result ) ) {
+		$status['errorCode']    = $skin->result->get_error_code();
+		$status['errorMessage'] = $skin->result->get_error_message();
+		wp_send_json_error( $status );
+	} elseif ( $skin->get_errors()->get_error_code() ) {
+		$status['errorMessage'] = $skin->get_error_messages();
+		wp_send_json_error( $status );
+	} elseif ( is_array( $result ) && ! empty( $result[ $plugin ] ) ) {
 		$plugin_update_data = current( $result );
 
 		/*
@@ -3716,9 +3752,6 @@
 			$status['newVersion'] = sprintf( __( 'Version %s' ), $plugin_data['Version'] );
 		}
 		wp_send_json_success( $status );
-	} elseif ( is_wp_error( $result ) ) {
-		$status['errorMessage'] = $result->get_error_message();
-		wp_send_json_error( $status );
 	} elseif ( false === $result ) {
 		global $wp_filesystem;
 
Index: src/wp-admin/includes/class-wp-ajax-upgrader-skin.php
===================================================================
--- src/wp-admin/includes/class-wp-ajax-upgrader-skin.php	(nonexistent)
+++ src/wp-admin/includes/class-wp-ajax-upgrader-skin.php	(working copy)
@@ -0,0 +1,132 @@
+<?php
+/**
+ * Upgrader API: WP_Ajax_Upgrader_Skin class
+ *
+ * @package WordPress
+ * @subpackage Upgrader
+ * @since 4.6.0
+ */
+
+/**
+ * Upgrader Skin for Ajax WordPress Upgrades
+ *
+ * This skin is designed to be used for Ajax updates.
+ *
+ * @since 4.6.0
+ *
+ * @see Automatic_Upgrader_Skin
+ */
+class WP_Ajax_Upgrader_Skin extends Automatic_Upgrader_Skin {
+	/**
+	 * Holds the WP_Error object.
+	 *
+	 * @since 4.6.0
+	 * @access protected
+	 *
+	 * @var null|WP_Error
+	 */
+	protected $errors = null;
+
+	/**
+	 * Constructor.
+	 *
+	 * @since 4.6.0
+	 * @access public
+	 *
+	 * @param array $args
+	 */
+	public function __construct( $args = array() ) {
+		parent::__construct( $args );
+
+		$this->errors = new WP_Error();
+	}
+
+	/**
+	 * Retrieves the list of errors.
+	 *
+	 * @since 4.6.0
+	 * @access public
+	 *
+	 * @return WP_Error Errors during an upgrade.
+	 */
+	public function get_errors() {
+		return $this->errors;
+	}
+
+	/**
+	 * Retrieves a string for error messages
+	 *
+	 * @since 4.6.0
+	 * @access public
+	 *
+	 * @return string Error messages during an upgrade.
+	 */
+	public function get_error_messages() {
+		$messages = array();
+
+		foreach ( $this->errors->get_error_codes() as $error_code ) {
+			if ( $this->errors->get_error_data( $error_code ) && is_string( $this->errors->get_error_data( $error_code ) ) ) {
+				$messages[] = $this->errors->get_error_message( $error_code ) . ' ' . esc_html( strip_tags( $this->errors->get_error_data( $error_code ) ) );
+			} else {
+				$messages[] = $this->errors->get_error_message( $error_code );
+			}
+		}
+
+		return implode( ', ', $messages );
+	}
+
+	/**
+	 * Stores a log entry for an error.
+	 *
+	 * @since 4.6.0
+	 * @access public
+	 *
+	 * @param string|WP_Error $errors
+	 */
+	public function error( $errors ) {
+		if ( is_string( $errors ) ) {
+			$string = $errors;
+			if ( ! empty( $this->upgrader->strings[ $string ] ) ) {
+				$string = $this->upgrader->strings[ $string ];
+			}
+
+			if ( false !== strpos( $string, '%' ) ) {
+				$args = func_get_args();
+				$args = array_splice( $args, 1 );
+				if ( ! empty( $args ) ) {
+					$string = vsprintf( $string, $args );
+				}
+			}
+
+			// Count existing errors to generate an unique error code.
+			$errors_count = count( $errors->get_error_codes() );
+			$this->errors->add( 'unknown_error_' . $errors_count + 1 , $string );
+		} elseif ( is_wp_error( $errors ) ) {
+			foreach ( $errors->get_error_codes() as $error_code ) {
+				$this->errors->add( $error_code, $errors->get_error_message( $error_code ), $errors->get_error_data( $error_code ) );
+			}
+		}
+
+		$args = func_get_args();
+		call_user_func_array( array( $this, 'parent::error' ), $args );
+	}
+
+	/**
+	 * Stores a log entry.
+	 *
+	 * @since 4.6.0
+	 * @access public
+	 *
+	 * @param string|array|WP_Error $data
+	 */
+	public function feedback( $data ) {
+		if ( is_wp_error( $data ) ) {
+			foreach ( $data->get_error_codes() as $error_code ) {
+				$this->errors->add( $error_code, $data->get_error_message( $error_code ), $data->get_error_data( $error_code ) );
+			}
+		}
+
+		$args = func_get_args();
+		call_user_func_array( array( $this, 'parent::feedback' ), $args );
+	}
+}
Index: src/wp-admin/includes/class-wp-upgrader-skins.php
===================================================================
--- src/wp-admin/includes/class-wp-upgrader-skins.php	(revision 38182)
+++ src/wp-admin/includes/class-wp-upgrader-skins.php	(working copy)
@@ -36,3 +36,6 @@
 
 /** Automatic_Upgrader_Skin class */
 require_once ABSPATH . 'wp-admin/includes/class-automatic-upgrader-skin.php';
+
+/** WP_Ajax_Upgrader_Skin class */
+require_once ABSPATH . 'wp-admin/includes/class-wp-ajax-upgrader-skin.php';
Index: src/wp-admin/includes/class-wp-upgrader.php
===================================================================
--- src/wp-admin/includes/class-wp-upgrader.php	(revision 38182)
+++ src/wp-admin/includes/class-wp-upgrader.php	(working copy)
@@ -39,6 +39,9 @@
 /** Automatic_Upgrader_Skin class */
 require_once ABSPATH . 'wp-admin/includes/class-automatic-upgrader-skin.php';
 
+/** WP_Ajax_Upgrader_Skin class */
+require_once ABSPATH . 'wp-admin/includes/class-wp-ajax-upgrader-skin.php';
+
 /**
  * Core class used for upgrading/installing a local set of files via
  * the Filesystem Abstraction classes from a Zip file.
Index: src/wp-admin/js/updates.js
===================================================================
--- src/wp-admin/js/updates.js	(revision 38182)
+++ src/wp-admin/js/updates.js	(working copy)
@@ -244,7 +244,7 @@
 	 * @param {string=} response.errorCode Optional. Error code for an error that occurred.
 	 */
 	wp.updates.ajaxAlways = function( response ) {
-		if ( ! response.errorCode && 'unable_to_connect_to_filesystem' !== response.errorCode ) {
+		if ( ! response.errorCode || 'unable_to_connect_to_filesystem' !== response.errorCode ) {
 			wp.updates.ajaxLocked = false;
 			wp.updates.queueChecker();
 		}
@@ -526,7 +526,7 @@
 		$card.removeClass( 'plugin-card-install-failed' ).find( '.notice.notice-error' ).remove();
 
 		$document.trigger( 'wp-plugin-installing', args );
-		
+
 		return wp.updates.ajax( 'install-plugin', args );
 	};
 
@@ -718,7 +718,7 @@
 		wp.a11y.speak( wp.updates.l10n.deleting, 'polite' );
 
 		$document.trigger( 'wp-plugin-deleting', args );
-		
+
 		return wp.updates.ajax( 'delete-plugin', args );
 	};
 
@@ -1043,7 +1043,7 @@
 		$( '.install-theme-info, [data-slug="' + args.slug + '"]' ).removeClass( 'theme-install-failed' ).find( '.notice.notice-error' ).remove();
 
 		$document.trigger( 'wp-theme-installing', args );
-		
+
 		return wp.updates.ajax( 'install-theme', args );
 	};
 
@@ -1174,7 +1174,7 @@
 		$( '.theme-info .update-message' ).remove();
 
 		$document.trigger( 'wp-theme-deleting', args );
-		
+
 		return wp.updates.ajax( 'delete-theme', args );
 	};
 
@@ -1525,7 +1525,7 @@
 	 * @returns {boolean} Whether there is an error that needs to be handled or not.
 	 */
 	wp.updates.maybeHandleCredentialError = function( response, action ) {
-		if ( response.errorCode && 'unable_to_connect_to_filesystem' === response.errorCode ) {
+		if ( wp.updates.shouldRequestFilesystemCredentials && response.errorCode && 'unable_to_connect_to_filesystem' === response.errorCode ) {
 			wp.updates.credentialError( response, action );
 			return true;
 		}
@@ -1982,7 +1982,7 @@
 			$bulkActionForm.find( '.manage-column [type="checkbox"]' ).prop( 'checked', false );
 
 			$document.trigger( 'wp-' + type + '-bulk-' + bulkAction, itemsSelected );
-			
+
 			// Find all the checkboxes which have been checked.
 			itemsSelected.each( function( index, element ) {
 				var $checkbox  = $( element ),
