diff --git a/src/Controller/InstallerController.php b/src/Controller/InstallerController.php
index 42029ae6ff034f68bcb51190efcbd8ab0d5cd3a6..8b0f39798fe06fa56d33e15cf4a32f7077a6fc3e 100644
--- a/src/Controller/InstallerController.php
+++ b/src/Controller/InstallerController.php
@@ -205,7 +205,7 @@ class InstallerController extends ControllerBase {
    * @param string $phase
    *   The phase the request was made in.
    * @param string|null $stage_id
-   *   The stage id of the installer within the request.
+   *   The stage ID of the installer within the request.
    *
    * @return \Symfony\Component\HttpFoundation\JsonResponse
    *   Provides information about the completed operation.
@@ -250,17 +250,12 @@ class InstallerController extends ControllerBase {
    *   handler.
    * @param string $phase
    *   The require phase in progress.
-   * @param string|null $stage_id
-   *   The stage ID, if known.
    */
-  private function setRequiringState(?string $id, string $phase, ?string $stage_id): void {
+  private function setRequiringState(?string $id, string $phase): void {
     $data = $this->projectBrowserTempStore->get('requiring') ?? [];
     if ($id) {
       $data['project_id'] = $id;
     }
-    if ($stage_id) {
-      $data['stage_id'] = $stage_id;
-    }
     $data['phase'] = $phase;
     $this->projectBrowserTempStore->set('requiring', $data);
   }
@@ -399,15 +394,12 @@ class InstallerController extends ControllerBase {
    * @param \Symfony\Component\HttpFoundation\Request $request
    *   The request.
    * @param string $stage_id
-   *   The stage id of the installer within the request.
+   *   The stage ID of the installer within the request.
    *
    * @return \Symfony\Component\HttpFoundation\JsonResponse
    *   Status message.
    */
   public function require(Request $request, string $stage_id): JsonResponse {
-    if (empty($stage_id)) {
-      throw new \InvalidArgumentException('The "stage_id" parameter is required.');
-    }
     $package_names = $package_ids = [];
     foreach ($request->toArray() as $project) {
       $project = $this->enabledSourceHandler->getStoredProject($project);
@@ -433,10 +425,10 @@ class InstallerController extends ControllerBase {
       );
       return new JsonResponse(['message' => $error_message], 500);
     }
-    $this->setRequiringState(implode(', ', $package_ids), 'requiring module', $stage_id);
+    $this->setRequiringState(implode(', ', $package_ids), 'requiring module');
     try {
       $this->installer->claim($stage_id)->require($package_names);
-      $this->setRequiringState(NULL, 'requiring module', NULL);
+      $this->setRequiringState(NULL, 'requiring module');
       return $this->successResponse('require', $stage_id);
     }
     catch (\Exception $e) {
@@ -448,12 +440,14 @@ class InstallerController extends ControllerBase {
   /**
    * Performs apply operations on the stage.
    *
+   * @param string $stage_id
+   *   The stage ID of the installer within the request.
+   *
    * @return \Symfony\Component\HttpFoundation\JsonResponse
    *   Status message.
    */
-  public function apply(): JsonResponse {
-    $stage_id = $this->projectBrowserTempStore->get('requiring')['stage_id'];
-    $this->setRequiringState(NULL, 'applying', NULL);
+  public function apply(string $stage_id): JsonResponse {
+    $this->setRequiringState(NULL, 'applying');
     try {
       $this->installer->claim($stage_id)->apply();
     }
@@ -467,12 +461,14 @@ class InstallerController extends ControllerBase {
   /**
    * Performs post apply operations on the stage.
    *
+   * @param string $stage_id
+   *   The stage ID of the installer within the request.
+   *
    * @return \Symfony\Component\HttpFoundation\JsonResponse
    *   Status message.
    */
-  public function postApply(): JsonResponse {
-    $stage_id = $this->projectBrowserTempStore->get('requiring')['stage_id'];
-    $this->setRequiringState(NULL, 'post apply', NULL);
+  public function postApply(string $stage_id): JsonResponse {
+    $this->setRequiringState(NULL, 'post apply');
     try {
       $this->installer->claim($stage_id)->postApply();
     }
@@ -485,12 +481,14 @@ class InstallerController extends ControllerBase {
   /**
    * Performs destroy operations on the stage.
    *
+   * @param string $stage_id
+   *   The stage ID of the installer within the request.
+   *
    * @return \Symfony\Component\HttpFoundation\JsonResponse
    *   Status message.
    */
-  public function destroy(): JsonResponse {
-    $stage_id = $this->projectBrowserTempStore->get('requiring')['stage_id'];
-    $this->setRequiringState(NULL, 'completing', NULL);
+  public function destroy(string $stage_id): JsonResponse {
+    $this->setRequiringState(NULL, 'completing');
     try {
       $this->installer->claim($stage_id)->destroy();
     }
diff --git a/src/Routing/ProjectBrowserRoutes.php b/src/Routing/ProjectBrowserRoutes.php
index 77fa573397c3bd32dd8641993d2fd3fb38c90e8e..78ccdfc9787cdd3fc1323a65f909ef7e11a85e07 100644
--- a/src/Routing/ProjectBrowserRoutes.php
+++ b/src/Routing/ProjectBrowserRoutes.php
@@ -70,12 +70,13 @@ class ProjectBrowserRoutes implements ContainerInjectionInterface {
       [
         'requirements' => [
           '_format' => 'json',
+          'stage_id' => '\w+',
         ],
         'methods' => ['POST'],
       ]
     );
     $routes['project_browser.stage.apply'] = new Route(
-      '/admin/modules/project_browser/install-apply',
+      '/admin/modules/project_browser/install-apply/{stage_id}',
       [
         '_controller' => InstallerController::class . '::apply',
         '_title' => 'Apply phase',
@@ -84,9 +85,14 @@ class ProjectBrowserRoutes implements ContainerInjectionInterface {
         '_permission' => 'administer modules',
         '_custom_access' => InstallerController::class . '::access',
       ],
+      [
+        'requirements' => [
+          'stage_id' => '\w+',
+        ],
+      ]
     );
     $routes['project_browser.stage.post_apply'] = new Route(
-      '/admin/modules/project_browser/install-post_apply',
+      '/admin/modules/project_browser/install-post_apply/{stage_id}',
       [
         '_controller' => InstallerController::class . '::postApply',
         '_title' => 'Post apply phase',
@@ -95,9 +101,14 @@ class ProjectBrowserRoutes implements ContainerInjectionInterface {
         '_permission' => 'administer modules',
         '_custom_access' => InstallerController::class . '::access',
       ],
+      [
+        'requirements' => [
+          'stage_id' => '\w+',
+        ],
+      ]
     );
     $routes['project_browser.stage.destroy'] = new Route(
-      '/admin/modules/project_browser/install-destroy',
+      '/admin/modules/project_browser/install-destroy/{stage_id}',
       [
         '_controller' => InstallerController::class . '::destroy',
         '_title' => 'Destroy phase',
@@ -106,6 +117,11 @@ class ProjectBrowserRoutes implements ContainerInjectionInterface {
         '_permission' => 'administer modules',
         '_custom_access' => InstallerController::class . '::access',
       ],
+      [
+        'requirements' => [
+          'stage_id' => '\w+',
+        ],
+      ]
     );
     $routes['project_browser.activate'] = new Route(
       '/admin/modules/project_browser/activate',
diff --git a/sveltejs/public/build/bundle.js b/sveltejs/public/build/bundle.js
index d8d036821279416c21a68ae022bddeba3c18b838..ba1a910b42441fc95cb547143e9169d13979e40f 100644
Binary files a/sveltejs/public/build/bundle.js and b/sveltejs/public/build/bundle.js differ
diff --git a/sveltejs/public/build/bundle.js.map b/sveltejs/public/build/bundle.js.map
index f3ec12db1d27e947ec93aca2008ed0cfb53a000d..156808395cb440d20f308e4c11d092edc06149ce 100644
Binary files a/sveltejs/public/build/bundle.js.map and b/sveltejs/public/build/bundle.js.map differ
diff --git a/sveltejs/src/ProcessQueueButton.svelte b/sveltejs/src/ProcessQueueButton.svelte
index 9f2e62cd0373868c039acfa91d4f718b1aa7dadc..1b328d1b10a88c3873141b14cc776054ad09ef84 100644
--- a/sveltejs/src/ProcessQueueButton.svelte
+++ b/sveltejs/src/ProcessQueueButton.svelte
@@ -114,15 +114,15 @@
           method: 'POST',
         },
         {
-          url: `${BASE_URL}admin/modules/project_browser/install-apply`,
+          url: `${BASE_URL}admin/modules/project_browser/install-apply/${stageId}`,
           method: 'GET',
         },
         {
-          url: `${BASE_URL}admin/modules/project_browser/install-post_apply`,
+          url: `${BASE_URL}admin/modules/project_browser/install-post_apply/${stageId}`,
           method: 'GET',
         },
         {
-          url: `${BASE_URL}admin/modules/project_browser/install-destroy`,
+          url: `${BASE_URL}admin/modules/project_browser/install-destroy/${stageId}`,
           method: 'GET',
         },
       ];
diff --git a/tests/src/Functional/InstallerControllerTest.php b/tests/src/Functional/InstallerControllerTest.php
index 33a27ad18a9a2731a0ecc840f36e93f67fd44f57..7d1488e853fb63a84a8d34d149c7cf0ffb3bdc00 100644
--- a/tests/src/Functional/InstallerControllerTest.php
+++ b/tests/src/Functional/InstallerControllerTest.php
@@ -244,8 +244,8 @@ class InstallerControllerTest extends BrowserTestBase {
    */
   private function doStart() {
     $this->assertProjectBrowserTempStatus(NULL, NULL);
-    $this->drupalGet('admin/modules/project_browser/install-begin');
-    $this->stageId = $this->sharedTempStore->get('package_manager_stage')->get('lock')[0];
+    $contents = $this->drupalGet('admin/modules/project_browser/install-begin');
+    $this->stageId = Json::decode($contents)['stage_id'];
     $this->assertSession()->statusCodeEquals(200);
     $expected_output = sprintf('{"phase":"create","status":0,"stage_id":"%s"}', $this->stageId);
   }
@@ -270,7 +270,7 @@ class InstallerControllerTest extends BrowserTestBase {
    * @covers ::apply
    */
   private function doApply() {
-    $this->drupalGet("/admin/modules/project_browser/install-apply");
+    $this->drupalGet("/admin/modules/project_browser/install-apply/$this->stageId");
     $expected_output = sprintf('{"phase":"apply","status":0,"stage_id":"%s"}', $this->stageId);
     $this->assertSame($expected_output, $this->getSession()->getPage()->getContent());
     $this->assertInstallInProgress('project_browser_test_mock/awesome_module', 'applying');
@@ -282,7 +282,7 @@ class InstallerControllerTest extends BrowserTestBase {
    * @covers ::postApply
    */
   private function doPostApply() {
-    $this->drupalGet("/admin/modules/project_browser/install-post_apply");
+    $this->drupalGet("/admin/modules/project_browser/install-post_apply/$this->stageId");
     $expected_output = sprintf('{"phase":"post apply","status":0,"stage_id":"%s"}', $this->stageId);
     $this->assertSame($expected_output, $this->getSession()->getPage()->getContent());
     $this->assertInstallInProgress('project_browser_test_mock/awesome_module', 'post apply');
@@ -294,7 +294,7 @@ class InstallerControllerTest extends BrowserTestBase {
    * @covers ::destroy
    */
   private function doDestroy() {
-    $this->drupalGet("/admin/modules/project_browser/install-destroy");
+    $this->drupalGet("/admin/modules/project_browser/install-destroy/$this->stageId");
     $expected_output = sprintf('{"phase":"destroy","status":0,"stage_id":"%s"}', $this->stageId);
     $this->assertSame($expected_output, $this->getSession()->getPage()->getContent());
     $this->assertInstallNotInProgress('awesome_module');
@@ -411,7 +411,7 @@ class InstallerControllerTest extends BrowserTestBase {
     TestSubscriber::setTestResult([$result], PreApplyEvent::class);
     $this->doStart();
     $this->doRequire();
-    $contents = $this->drupalGet("/admin/modules/project_browser/install-apply");
+    $contents = $this->drupalGet("/admin/modules/project_browser/install-apply/$this->stageId");
     $this->assertSession()->statusCodeEquals(500);
     $this->assertSame('{"message":"StageEventException: This is a PreApply error.\n","phase":"apply"}', $contents);
   }
@@ -426,7 +426,7 @@ class InstallerControllerTest extends BrowserTestBase {
     TestSubscriber::setException($error, PreApplyEvent::class);
     $this->doStart();
     $this->doRequire();
-    $contents = $this->drupalGet("/admin/modules/project_browser/install-apply");
+    $contents = $this->drupalGet("/admin/modules/project_browser/install-apply/$this->stageId");
     $this->assertSession()->statusCodeEquals(500);
     $this->assertSame('{"message":"StageEventException: PreApply did not go well.","phase":"apply"}', $contents);
   }
@@ -442,7 +442,7 @@ class InstallerControllerTest extends BrowserTestBase {
     $this->doStart();
     $this->doRequire();
     $this->doApply();
-    $contents = $this->drupalGet("/admin/modules/project_browser/install-post_apply");
+    $contents = $this->drupalGet("/admin/modules/project_browser/install-post_apply/$this->stageId");
     $this->assertSession()->statusCodeEquals(500);
     $this->assertSame('{"message":"StageEventException: PostApply did not go well.","phase":"post apply"}', $contents);
   }
@@ -587,9 +587,6 @@ class InstallerControllerTest extends BrowserTestBase {
     if (!is_null($phase)) {
       $expect_install['phase'] = $phase;
     }
-    if (!empty($this->stageId)) {
-      $expect_install['stage_id'] = $this->stageId;
-    }
     $this->assertProjectBrowserTempStatus($expect_install, NULL);
     $this->drupalGet("/admin/modules/project_browser/install_in_progress/$project_id");
     $this->assertSame(sprintf('{"status":1,"phase":"%s"}', $phase), $this->getSession()->getPage()->getContent());