diff --git a/automatic_updates.install b/automatic_updates.install
index 3c54d76b6443422ea487ea55c70431b8db5c4f36..0b5505b3573adcc9c0bf8168f17a9d2b539ad764 100644
--- a/automatic_updates.install
+++ b/automatic_updates.install
@@ -42,8 +42,8 @@ function _automatic_updates_checker_requirements(array &$requirements) {
     'severity' => REQUIREMENT_OK,
     'value' => t('Your site is ready to for <a href="@readiness_checks">automatic updates</a>.', ['@readiness_checks' => 'https://www.drupal.org/docs/8/update/automatic-updates#readiness-checks']),
   ];
-  $error_results = $checker->getResults('error');
-  $warning_results = $checker->getResults('warning');
+  $error_results = $checker->getResults(ReadinessCheckerManagerInterface::ERROR);
+  $warning_results = $checker->getResults(ReadinessCheckerManagerInterface::WARNING);
   $checker_results = array_merge($error_results, $warning_results);
   if (!empty($checker_results)) {
     $requirements['automatic_updates_readiness']['severity'] = $error_results ? REQUIREMENT_ERROR : REQUIREMENT_WARNING;
diff --git a/automatic_updates.module b/automatic_updates.module
index 446d8e99d882bf30d993805f7cf75aa5f740d55c..94c66ca60cef70666268203c55fc3b553cd6f708 100644
--- a/automatic_updates.module
+++ b/automatic_updates.module
@@ -46,7 +46,7 @@ function automatic_updates_page_top(array &$page_top) {
     }
     /** @var \Drupal\automatic_updates\ReadinessChecker\ReadinessCheckerManagerInterface $checker */
     $checker = \Drupal::service('automatic_updates.readiness_checker');
-    $results = $checker->getResults('error');
+    $results = $checker->getResults(ReadinessCheckerManagerInterface::ERROR);
     if ($results) {
       \Drupal::messenger()->addError(t('Your site is currently failing readiness checks for automatic updates. It cannot be <a href="@readiness_checks">automatically updated</a> until further action is performed:', ['@readiness_checks' => 'https://www.drupal.org/docs/8/update/automatic-updates#readiness-checks']));
       foreach ($results as $message) {
diff --git a/src/Form/SettingsForm.php b/src/Form/SettingsForm.php
index 6c52fea90a4d9353bee20032b7570fa80560416a..8869cf8831506af4fb5a947b047e6a331e092a18 100644
--- a/src/Form/SettingsForm.php
+++ b/src/Form/SettingsForm.php
@@ -103,22 +103,45 @@ class SettingsForm extends ConfigFormBase {
         ],
       ],
     ];
+
+    $form['experimental'] = [
+      '#type' => 'details',
+      '#title' => t('Experimental'),
+    ];
+    $form['experimental']['update'] = [
+      '#type' => 'html_tag',
+      '#tag' => 'p',
+      '#value' => $this->t('No update for Drupal is available for version %version.', ['%version' => \Drupal::VERSION]),
+    ];
     if (strpos(\Drupal::VERSION, '-dev') === FALSE) {
-      $version_array = explode('.', \Drupal::VERSION);
-      $version_array[2]++;
-      $next_version = implode('.', $version_array);
-      $form['experimental'] = [
-        '#type' => 'details',
-        '#title' => t('Experimental'),
-      ];
-      $form['experimental']['update']['#markup'] = $this->t('Very experimental. Might break the site. No checks. Just update the files of Drupal core. <a href="@link">Update now</a>. Note: database updates are not run.', [
-        '@link' => Url::fromRoute('automatic_updates.inplace-update', [
-          'project' => 'drupal',
-          'type' => 'core',
-          'from' => \Drupal::VERSION,
-          'to' => $next_version,
-        ])->toString(),
-      ]);
+      \Drupal::service('update.manager')->refreshUpdateData();
+      $available = update_get_available(TRUE);
+      $data = update_calculate_project_data($available);
+      // If we aren't on the recommended version for our version of Drupal, then
+      // enable this experimental feature.
+      if ($data['drupal']['existing_version'] !== $data['drupal']['recommended']) {
+        if (isset($data['drupal']['security updates'])) {
+          $form['experimental']['security'] = [
+            '#type' => 'html_tag',
+            '#tag' => 'p',
+            '#value' => $this->t('A security update is available for your version of Drupal.'),
+            '#weight' => -1,
+          ];
+        }
+        $form['experimental']['update'] = [
+          '#type' => 'html_tag',
+          '#tag' => 'p',
+          '#value' => $this->t('Even with all that caution, if you want to try it out, <a href="@link">update now</a>.', [
+            '@link' => Url::fromRoute('automatic_updates.inplace-update', [
+              'project' => 'drupal',
+              'type' => 'core',
+              'from' => \Drupal::VERSION,
+              'to' => $data['drupal']['latest_version'],
+            ])->toString(),
+          ]),
+          '#prefix' => 'Note: Might break the site. No readiness checks or anything in place. Just update the files of Drupal core. Database updates are not run.',
+        ];
+      }
     }
 
     return parent::buildForm($form, $form_state);
diff --git a/src/ProjectInfoTrait.php b/src/ProjectInfoTrait.php
index b3790f6c65b36486f7b6bdf68954de2fb09460b8..9572c671da29ebb615849bbe640d677345593b54 100644
--- a/src/ProjectInfoTrait.php
+++ b/src/ProjectInfoTrait.php
@@ -41,13 +41,20 @@ trait ProjectInfoTrait {
   protected function getInfos($extension_type) {
     $file_paths = $this->getExtensionList($extension_type)->getPathnames();
     $infos = $this->getExtensionList($extension_type)->getAllAvailableInfo();
-    return array_map(function ($key, array $info) use ($file_paths) {
+    array_walk($infos, function (array &$info, $key) use ($file_paths) {
       $info['packaged'] = $info['project'] ?? FALSE;
-      $info['project'] = $this->getProjectName($key, $info);
       $info['install path'] = $file_paths[$key] ? dirname($file_paths[$key]) : '';
+      $info['project'] = $this->getProjectName($key, $info);
       $info['version'] = $this->getExtensionVersion($info);
-      return $info;
-    }, array_keys($infos), $infos);
+    });
+    $system = $infos['system'] ?? NULL;
+    $infos = array_filter($infos, function (array $info, $project_name) {
+      return $info && $info['project'] === $project_name;
+    }, ARRAY_FILTER_USE_BOTH);
+    if ($system) {
+      $infos['drupal'] = $system;
+    }
+    return $infos;
   }
 
   /**
@@ -103,7 +110,7 @@ trait ProjectInfoTrait {
         $project_name = $this->getSuffix($composer_json['name'], '/', $extension_name);
       }
     }
-    if ($project_name === 'system') {
+    if (substr($info['install path'], 0, 4) === "core") {
       $project_name = 'drupal';
     }
     return $project_name;
diff --git a/src/ReadinessChecker/ModifiedFiles.php b/src/ReadinessChecker/ModifiedFiles.php
index c96e25e230d53ef29066f2866cf506f6e7747b51..12dda13ddb30ade415aaee55ea2430df2afa6f67 100644
--- a/src/ReadinessChecker/ModifiedFiles.php
+++ b/src/ReadinessChecker/ModifiedFiles.php
@@ -87,12 +87,9 @@ class ModifiedFiles implements ReadinessCheckerInterface {
     $messages = [];
     $extensions = [];
     foreach ($this->getExtensionsTypes() as $extension_type) {
-      foreach ($this->getInfos($extension_type) as $info) {
-        if (substr($info['install path'], 0, 4) !== 'core' || $info['project'] === 'drupal') {
-          $extensions[$info['project']] = $info;
-        }
-      }
+      $extensions[] = $this->getInfos($extension_type);
     }
+    $extensions = array_merge(...$extensions);
     $filtered_modified_files = new IgnoredPathsIteratorFilter($this->modifiedFiles->getModifiedFiles($extensions));
     foreach ($filtered_modified_files as $file) {
       $messages[] = $this->t('The hash for @file does not match its original. Updates that include that file will fail and require manual intervention.', ['@file' => $file]);
diff --git a/src/ReadinessChecker/ReadinessCheckerManager.php b/src/ReadinessChecker/ReadinessCheckerManager.php
index f41ccc5c16cbdf864911980e419542863913ca0c..443d552b83bb7e2909029647058f3b3e34706ce7 100644
--- a/src/ReadinessChecker/ReadinessCheckerManager.php
+++ b/src/ReadinessChecker/ReadinessCheckerManager.php
@@ -107,7 +107,7 @@ class ReadinessCheckerManager implements ReadinessCheckerManagerInterface {
    * {@inheritdoc}
    */
   public function getCategories() {
-    return ['warning', 'error'];
+    return [self::ERROR, self::WARNING];
   }
 
   /**
diff --git a/src/ReadinessChecker/ReadinessCheckerManagerInterface.php b/src/ReadinessChecker/ReadinessCheckerManagerInterface.php
index f63a40e007b052e072d44340dd0edea1bd274fa6..09523eafac7d15eae8d37f298becaa3e03394a8e 100644
--- a/src/ReadinessChecker/ReadinessCheckerManagerInterface.php
+++ b/src/ReadinessChecker/ReadinessCheckerManagerInterface.php
@@ -7,6 +7,16 @@ namespace Drupal\automatic_updates\ReadinessChecker;
  */
 interface ReadinessCheckerManagerInterface {
 
+  /**
+   * Error category.
+   */
+  const ERROR = 'error';
+
+  /**
+   * Warning category.
+   */
+  const WARNING = 'warning';
+
   /**
    * Last checked ago warning (in seconds).
    */
diff --git a/src/Services/InPlaceUpdate.php b/src/Services/InPlaceUpdate.php
index d37158f559a4cb1fce7500a45b273c1f21ab5d3c..03fd23eec31c831a5e17675277a51524d2ff5a14 100644
--- a/src/Services/InPlaceUpdate.php
+++ b/src/Services/InPlaceUpdate.php
@@ -2,6 +2,8 @@
 
 namespace Drupal\automatic_updates\Services;
 
+use Drupal\automatic_updates\ProjectInfoTrait;
+use Drupal\automatic_updates\ReadinessChecker\ReadinessCheckerManagerInterface;
 use Drupal\Core\Archiver\ArchiverInterface;
 use Drupal\Core\Archiver\ArchiverManager;
 use Drupal\Core\Config\ConfigFactoryInterface;
@@ -20,6 +22,7 @@ use Psr\Log\LoggerInterface;
  * Class to apply in-place updates.
  */
 class InPlaceUpdate implements UpdateInterface {
+  use ProjectInfoTrait;
 
   /**
    * The manifest file that lists all file deletions.
@@ -130,6 +133,12 @@ class InPlaceUpdate implements UpdateInterface {
    * {@inheritdoc}
    */
   public function update($project_name, $project_type, $from_version, $to_version) {
+    // Bail immediately on updates if error category checks fail.
+    /** @var \Drupal\automatic_updates\ReadinessChecker\ReadinessCheckerManagerInterface $readiness_check_manager */
+    $checker = \Drupal::service('automatic_updates.readiness_checker');
+    if ($checker->run(ReadinessCheckerManagerInterface::ERROR)) {
+      return FALSE;
+    }
     $success = FALSE;
     if ($project_name === 'drupal') {
       $project_root = $this->rootPath;
@@ -138,11 +147,12 @@ class InPlaceUpdate implements UpdateInterface {
       $project_root = drupal_get_path($project_type, $project_name);
     }
     if ($archive = $this->getArchive($project_name, $from_version, $to_version)) {
-      if ($this->backup($archive, $project_root)) {
+      $modified = $this->checkModifiedFiles($project_name, $project_type, $archive);
+      if (!$modified && $this->backup($archive, $project_root)) {
         $success = $this->processUpdate($archive, $project_root);
-      }
-      if (!$success) {
-        $this->rollback($project_root);
+        if (!$success) {
+          $this->rollback($project_root);
+        }
       }
     }
     return $success;
@@ -169,6 +179,53 @@ class InPlaceUpdate implements UpdateInterface {
     return $this->archiveManager->getInstance(['filepath' => $destination]);
   }
 
+  /**
+   * Check if files are modified before applying updates.
+   *
+   * @param string $project_name
+   *   The project name.
+   * @param string $project_type
+   *   The project type.
+   * @param \Drupal\Core\Archiver\ArchiverInterface $archive
+   *   The archive.
+   *
+   * @return bool
+   *   Return TRUE if modified files exist, FALSE otherwise.
+   */
+  protected function checkModifiedFiles($project_name, $project_type, ArchiverInterface $archive) {
+    $extensions = [];
+    foreach (['module', 'profile', 'theme'] as $extension_type) {
+      $extensions[] = $this->getInfos($extension_type);
+    }
+    $extensions = array_merge(...$extensions);
+
+    /** @var \Drupal\automatic_updates\Services\ModifiedFilesInterface $modified_files */
+    $modified_files = \Drupal::service('automatic_updates.modified_files');
+    $files = iterator_to_array($modified_files->getModifiedFiles([$extensions[$project_name]]));
+    $files = array_unique($files);
+    $archive_files = $archive->listContents();
+    foreach ($archive_files as $index => &$archive_file) {
+      $skipped_files = [
+        self::DELETION_MANIFEST,
+        self::CHECKSUM_LIST,
+      ];
+      // Skip certain files and all directories.
+      if (in_array($archive_file, $skipped_files, TRUE) || substr($archive_file, -1) === '/') {
+        unset($archive_files[$index]);
+        continue;
+      }
+      $this->stripFileDirectoryPath($archive_file);
+    }
+    if ($intersection = array_intersect($files, $archive_files)) {
+      $this->logger->error('Can not update because %count files are modified: %path', [
+        '%count' => count($intersection),
+        '%paths' => implode(', ', $intersection),
+      ]);
+      return TRUE;
+    }
+    return FALSE;
+  }
+
   /**
    * Perform retrieval of archive, with delay if archive is still being created.
    *
diff --git a/src/Services/ModifiedFiles.php b/src/Services/ModifiedFiles.php
index b900d53276ca30a7ef7296d1a447340ac3656fc1..98a3babdda051793f2809f0fdc815d6db07a5197 100644
--- a/src/Services/ModifiedFiles.php
+++ b/src/Services/ModifiedFiles.php
@@ -137,6 +137,10 @@ class ModifiedFiles implements ModifiedFilesInterface {
    */
   protected function getHashRequests(array $extensions) {
     foreach ($extensions as $info) {
+      // We can't check for modifications if we don't know the version.
+      if (!($info['version'])) {
+        continue;
+      }
       $url = $this->buildUrl($info);
       yield $this->getPromise($url, $info);
     }
diff --git a/tests/src/Build/InPlaceUpdateTest.php b/tests/src/Build/InPlaceUpdateTest.php
index b9c7f0b02ba5e15b69b43e104ca3d671ea38c23a..fdd4abf9764481bb486d85297077aad4d3545e1e 100644
--- a/tests/src/Build/InPlaceUpdateTest.php
+++ b/tests/src/Build/InPlaceUpdateTest.php
@@ -178,14 +178,8 @@ class InPlaceUpdateTest extends QuickStartTestBase {
    * Core versions data provider.
    */
   public function coreVersionsProvider() {
-    $datum[] = [
-      'from' => '8.7.0',
-      'to' => '8.7.1',
-    ];
-    $datum[] = [
-      'from' => '8.7.1',
-      'to' => '8.7.2',
-    ];
+    // 8.7.2 has changes to composer.lock, therefore it currently fails update.
+    // TODO: https://www.drupal.org/project/automatic_updates/issues/3088095
     $datum[] = [
       'from' => '8.7.2',
       'to' => '8.7.3',
diff --git a/tests/src/Kernel/ReadinessChecker/MissingProjectInfoTest.php b/tests/src/Kernel/ReadinessChecker/MissingProjectInfoTest.php
index 9929506521758f5aac1ea26e1d2d461132139edb..f01848d53d1fb9165e7384638f366196e13e254a 100644
--- a/tests/src/Kernel/ReadinessChecker/MissingProjectInfoTest.php
+++ b/tests/src/Kernel/ReadinessChecker/MissingProjectInfoTest.php
@@ -65,7 +65,7 @@ class TestMissingProjectInfo extends MissingProjectInfo {
         'package' => 'Core',
         'version' => 'VERSION',
         'packaged' => FALSE,
-        'project' => $this->getProjectName('system', []),
+        'project' => $this->getProjectName('system', ['install path' => 'core']),
         'install path' => drupal_get_path('module', 'system'),
         'core' => '8.x',
         'required' => 'true',
diff --git a/tests/src/Kernel/ReadinessChecker/ReadinessCheckerTest.php b/tests/src/Kernel/ReadinessChecker/ReadinessCheckerTest.php
index 228b73c5bfe7bb36b36a60903fa58e83c79b4fd0..0d50ef0050c7cc6006a447d61aedf4d5bb36e65e 100644
--- a/tests/src/Kernel/ReadinessChecker/ReadinessCheckerTest.php
+++ b/tests/src/Kernel/ReadinessChecker/ReadinessCheckerTest.php
@@ -26,8 +26,9 @@ class ReadinessCheckerTest extends KernelTestBase {
   public function testReadinessChecker() {
     /** @var \Drupal\automatic_updates\ReadinessChecker\ReadinessCheckerManagerInterface $checker */
     $checker = $this->container->get('automatic_updates.readiness_checker');
-    $this->assertEmpty($checker->run('warning'));
-    $this->assertEmpty($checker->run('error'));
+    foreach ($checker->getCategories() as $category) {
+      $this->assertEmpty($checker->run($category));
+    }
   }
 
 }