diff --git a/.gitlab-ci/pipeline.yml b/.gitlab-ci/pipeline.yml
index 3196e507822678d6416c0d04f40cc481855d197e..06370dca2e872716dd48d952c856ee2c52f9bdce 100644
--- a/.gitlab-ci/pipeline.yml
+++ b/.gitlab-ci/pipeline.yml
@@ -113,7 +113,7 @@ variables:
         echo '   "Key" to "REPEAT_TEST_CLASS" and "Value" to "Drupal\Tests\ckeditor5\FunctionalJavascript\MediaLinkabilityTest"';
         exit 1;
       else
-        sudo -u www-data -E -H php ./core/scripts/run-tests.sh --color --keep-results --concurrency "$CONCURRENCY" --repeat "100" --sqlite "./sites/default/files/tests.sqlite" --dburl $SIMPLETEST_DB --url $SIMPLETEST_BASE_URL --verbose --non-html --class $REPEAT_TEST_CLASS
+        sudo -u www-data -E -H php ./core/scripts/run-tests.sh --color --keep-results --concurrency "$CONCURRENCY" --repeat "50" --sqlite "./sites/default/files/tests.sqlite" --dburl $SIMPLETEST_DB --url $SIMPLETEST_BASE_URL --verbose --non-html --class $REPEAT_TEST_CLASS
       fi
 
 ################
@@ -274,10 +274,8 @@ variables:
 
 '🔁 Repeat Class Test':
   <<: [ *with-composer, *run-repeat-class-test, *default-job-settings ]
-  when: manual
-  allow_failure: true
   variables:
-    REPEAT_TEST_CLASS: 'Drupal\Tests\Change\Me'
+    REPEAT_TEST_CLASS: '\Drupal\Tests\package_manager\Build\PackageUpdateTest'
   services:
     - <<: *with-database
 
diff --git a/composer-stager-414.patch b/composer-stager-414.patch
new file mode 100644
index 0000000000000000000000000000000000000000..38172203d3d4bf6e7992fdf8eefcd9533cfed77c
--- /dev/null
+++ b/composer-stager-414.patch
@@ -0,0 +1,29 @@
+diff --git a/src/Internal/FileSyncer/Service/FileSyncer.php b/src/Internal/FileSyncer/Service/FileSyncer.php
+index 25a9cfff..129f7413 100644
+--- a/src/Internal/FileSyncer/Service/FileSyncer.php
++++ b/src/Internal/FileSyncer/Service/FileSyncer.php
+@@ -55,7 +55,7 @@ public function sync(
+         $this->assertSourceAndDestinationAreDifferent($source, $destination);
+         $this->assertSourceIsValid($source);
+
+-        $this->runCommand($exclusions, $source, $destination, $callback);
++        $this->runCommand($exclusions, $source, $destination, $callback, $timeout);
+     }
+
+     /** @infection-ignore-all This only makes any difference on Windows, whereas Infection is only run on Linux. */
+@@ -113,6 +113,7 @@ private function runCommand(
+         PathInterface $source,
+         PathInterface $destination,
+         ?OutputCallbackInterface $callback,
++        int $timeout,
+     ): void {
+         $this->ensureDestinationDirectoryExists($destination);
+         $command = $this->buildCommand($source, $destination, $exclusions);
+@@ -123,6 +124,7 @@ private function runCommand(
+                 $this->pathFactory->create('/', $source),
+                 [],
+                 $callback,
++                $timeout,
+             );
+         } catch (ExceptionInterface $e) {
+             throw new IOException($e->getTranslatableMessage(), 0, $e);
diff --git a/composer.json b/composer.json
index 659cbf36d8f32bf774ac77ad4608d7732c1a6ce4..1f6460323e3f5df00699ab24ac2959971afc204d 100644
--- a/composer.json
+++ b/composer.json
@@ -19,6 +19,7 @@
         "behat/mink-browserkit-driver": "^2.2",
         "colinodell/psr-testlogger": "^1.2",
         "composer/composer": "^2.8.1",
+        "cweagans/composer-patches": "^1.7",
         "drupal/coder": "^8.3.10",
         "justinrainbow/json-schema": "^5.2",
         "lullabot/mink-selenium2-driver": "^1.7.3",
@@ -65,10 +66,16 @@
             "drupal/core-vendor-hardening": true,
             "php-http/discovery": true,
             "phpstan/extension-installer": true,
-            "tbachert/spi": false
+            "tbachert/spi": false,
+            "cweagans/composer-patches": true
         }
     },
     "extra": {
+        "patches": {
+            "php-tuf/composer-stager": {
+                "Pass FileSyncer timeout to AbstractProcessRunner::run": "./composer-stager-414.patch"
+            }
+        },
         "_readme": [
             "By default Drupal loads the autoloader from ./vendor/autoload.php.",
             "To change the autoloader you can edit ./autoload.php.",
diff --git a/composer.lock b/composer.lock
index 86e0d6df2327e5d28d2fea76a2aeb029d904dc16..241a9bf891b2109a47c0a4e78cbebfdfdf86fde1 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,7 +4,7 @@
         "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
         "This file is @generated automatically"
     ],
-    "content-hash": "a6a454e0a25ad4c4d26cd760d50eb4d0",
+    "content-hash": "37a7635547d62acc810e988795dd8c6e",
     "packages": [
         {
             "name": "asm89/stack-cors",
@@ -5325,6 +5325,54 @@
             ],
             "time": "2024-05-06T16:37:16+00:00"
         },
+        {
+            "name": "cweagans/composer-patches",
+            "version": "1.7.3",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/cweagans/composer-patches.git",
+                "reference": "e190d4466fe2b103a55467dfa83fc2fecfcaf2db"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/cweagans/composer-patches/zipball/e190d4466fe2b103a55467dfa83fc2fecfcaf2db",
+                "reference": "e190d4466fe2b103a55467dfa83fc2fecfcaf2db",
+                "shasum": ""
+            },
+            "require": {
+                "composer-plugin-api": "^1.0 || ^2.0",
+                "php": ">=5.3.0"
+            },
+            "require-dev": {
+                "composer/composer": "~1.0 || ~2.0",
+                "phpunit/phpunit": "~4.6"
+            },
+            "type": "composer-plugin",
+            "extra": {
+                "class": "cweagans\\Composer\\Patches"
+            },
+            "autoload": {
+                "psr-4": {
+                    "cweagans\\Composer\\": "src"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Cameron Eagans",
+                    "email": "me@cweagans.net"
+                }
+            ],
+            "description": "Provides a way to patch Composer packages.",
+            "support": {
+                "issues": "https://github.com/cweagans/composer-patches/issues",
+                "source": "https://github.com/cweagans/composer-patches/tree/1.7.3"
+            },
+            "time": "2022-12-20T22:53:13+00:00"
+        },
         {
             "name": "dealerdirect/phpcodesniffer-composer-installer",
             "version": "v1.0.0",
diff --git a/composer/Metapackage/DevDependencies/composer.json b/composer/Metapackage/DevDependencies/composer.json
index 023a65286fddea44422223bc0d1ce863e7ad1670..71d922ee7190a36d9dd98fdda9ae1349e3fd7f14 100644
--- a/composer/Metapackage/DevDependencies/composer.json
+++ b/composer/Metapackage/DevDependencies/composer.json
@@ -11,6 +11,7 @@
         "behat/mink-browserkit-driver": "^2.2",
         "colinodell/psr-testlogger": "^1.2",
         "composer/composer": "^2.8.1",
+        "cweagans/composer-patches": "^1.7",
         "drupal/coder": "^8.3.10",
         "justinrainbow/json-schema": "^5.2",
         "lullabot/mink-selenium2-driver": "^1.7.3",
diff --git a/composer/Metapackage/PinnedDevDependencies/composer.json b/composer/Metapackage/PinnedDevDependencies/composer.json
index ce382db155639ca43bb429b9af6661b1c0d7fada..913cf72e1acc4172701d0c4e6783aef0d846bece 100644
--- a/composer/Metapackage/PinnedDevDependencies/composer.json
+++ b/composer/Metapackage/PinnedDevDependencies/composer.json
@@ -19,6 +19,7 @@
         "composer/pcre": "3.3.2",
         "composer/spdx-licenses": "1.5.8",
         "composer/xdebug-handler": "3.0.5",
+        "cweagans/composer-patches": "1.7.3",
         "dealerdirect/phpcodesniffer-composer-installer": "v1.0.0",
         "doctrine/instantiator": "2.0.0",
         "drupal/coder": "8.3.26",
diff --git a/core/modules/package_manager/tests/modules/package_manager_test_api/src/ApiController.php b/core/modules/package_manager/tests/modules/package_manager_test_api/src/ApiController.php
index d2839be3947c6e68dc995642464bb1eb693e0442..b51c3544167e51ce156e474c6896a09c147bd19c 100644
--- a/core/modules/package_manager/tests/modules/package_manager_test_api/src/ApiController.php
+++ b/core/modules/package_manager/tests/modules/package_manager_test_api/src/ApiController.php
@@ -91,7 +91,7 @@ public function run(Request $request): RedirectResponse {
   public function finish(string $id): Response {
     $this->stage->claim($id)->postApply();
     $this->stage->destroy();
-    return new Response();
+    return new Response('Finished');
   }
 
   /**
diff --git a/core/modules/package_manager/tests/src/Build/TemplateProjectTestBase.php b/core/modules/package_manager/tests/src/Build/TemplateProjectTestBase.php
index 16cd486ad75008072cbc1cd3379bd05e6c736898..5bee6ef0ad996ba39cea2f74793cf9a3dec41402 100644
--- a/core/modules/package_manager/tests/src/Build/TemplateProjectTestBase.php
+++ b/core/modules/package_manager/tests/src/Build/TemplateProjectTestBase.php
@@ -291,6 +291,10 @@ protected function createTestProject(string $template): void {
     // It adds no value to this test.
     $data['config']['allow-plugins']['dealerdirect/phpcodesniffer-composer-installer'] = FALSE;
 
+    $data['require']['cweagans/composer-patches'] = '^1.7';
+    $data['config']['allow-plugins']['cweagans/composer-patches'] = TRUE;
+    $data['config']['extra']['composer-exit-on-patch-failure'] = TRUE;
+
     // Always force Composer to mirror path repositories. This is necessary
     // because dependencies are installed from a Composer-type repository, which
     // will normally try to symlink packages which are installed from local
@@ -719,6 +723,9 @@ protected function makePackageManagerTestApiRequest(string $url, array $query_da
       $this->serverErrorLog,
     );
     $this->assertSame(200, $session->getStatusCode(), $message);
+    // Sometimes we get a 200 response after a PHP timeout or OOM error, so we
+    // also check the page content to ensure it's what we expect.
+    $this->assertSame('Finished', $session->getPage()->getText());
   }
 
   /**
diff --git a/core/tests/Drupal/BuildTests/Framework/BuildTestBase.php b/core/tests/Drupal/BuildTests/Framework/BuildTestBase.php
index 313c6f54fa9d07eb3af8eddcbca060d98f8172ca..8ae2b1b3e49ef8e6dfed1df05dd8294ff0978cea 100644
--- a/core/tests/Drupal/BuildTests/Framework/BuildTestBase.php
+++ b/core/tests/Drupal/BuildTests/Framework/BuildTestBase.php
@@ -331,8 +331,8 @@ public function assertCommandExitCode($expected_code) {
   public function executeCommand($command_line, $working_dir = NULL) {
     $this->commandProcess = Process::fromShellCommandline($command_line);
     $this->commandProcess->setWorkingDirectory($this->getWorkingPath($working_dir))
-      ->setTimeout(300)
-      ->setIdleTimeout(300);
+      ->setTimeout(360)
+      ->setIdleTimeout(360);
     $this->commandProcess->run();
     return $this->commandProcess;
   }